diff --git a/.github/aur/stirling-pdf-desktop/PKGBUILD b/.github/aur/stirling-pdf-desktop/PKGBUILD index fb6a99cfca..c47c8e3ee7 100644 --- a/.github/aur/stirling-pdf-desktop/PKGBUILD +++ b/.github/aur/stirling-pdf-desktop/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Stirling PDF Inc pkgname=stirling-pdf-desktop -pkgver=2.14.2 +pkgver=2.14.3 pkgrel=1 pkgdesc="Locally hosted, web-based PDF manipulation tool (Tauri desktop app, official Stirling PDF Inc build)" arch=('x86_64') diff --git a/.github/aur/stirling-pdf-server-bin/PKGBUILD b/.github/aur/stirling-pdf-server-bin/PKGBUILD index 70bcee0423..d6159ddce3 100644 --- a/.github/aur/stirling-pdf-server-bin/PKGBUILD +++ b/.github/aur/stirling-pdf-server-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Stirling PDF Inc pkgname=stirling-pdf-server-bin -pkgver=2.14.2 +pkgver=2.14.3 pkgrel=1 pkgdesc="Locally hosted, web-based PDF manipulation tool (server JAR, prebuilt)" arch=('any') diff --git a/.github/config/.files.yaml b/.github/config/.files.yaml index b5cc0527b0..7936145677 100644 --- a/.github/config/.files.yaml +++ b/.github/config/.files.yaml @@ -4,10 +4,12 @@ # instead of matching only the project filter. ci: &ci - .github/workflows/build.yml + - .github/workflows/gradle-cache-prime.yml - .github/config/.files.yaml build: &build - *ci + - buildSrc/** - build.gradle - gradle/spotless.gradle - app/(common|core|proprietary|saas)/build.gradle @@ -15,6 +17,22 @@ build: &build - .taskfiles/backend.yml - .github/workflows/check-licence.yml +# Backend build inputs. This is intentionally broader than `build`: Java and +# backend resource changes must exercise the backend matrix even when Gradle +# build scripts themselves are unchanged. +backend: &backend + - *ci + - *build + - gradle/** + - gradle.properties + - gradlew + - gradlew.bat + - settings.gradle + - app/(common|core|proprietary|saas)/src/(main|test)/java/** + - "app/(common|core|proprietary|saas)/src/(main|test)/resources/**/!(messages_*.properties|*.md)*" + - scripts/db-migration/** + - .github/workflows/backend-build.yml + openapi: &openapi - *ci - *build diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 92de1d9503..883c4f7f46 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -65,6 +65,7 @@ updates: directories: - /devTools - /frontend + - /testing/compose/mcp-client-check schedule: interval: "weekly" cooldown: @@ -93,6 +94,13 @@ updates: - "react-dom" - "@types/react" - "@types/react-dom" + tanstack: + patterns: + - "@tanstack/*" + typescript: + patterns: + - "typescript" + - "@typescript/*" vite: patterns: - "vite" @@ -171,14 +179,6 @@ updates: - "tokio" - "tokio-*" - - package-ecosystem: pip - directory: /testing/cucumber - schedule: - interval: "weekly" - cooldown: - default-days: 7 - rebase-strategy: "auto" - - package-ecosystem: "uv" directory: "/engine" schedule: diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index 0f07aabbe5..2c9f6e2e26 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -462,7 +462,10 @@ jobs: }); cleanup-v2-deployment: - environment: pr-preview + # Tearing a preview down is not a deployment - no deployment object. + environment: + name: pr-preview + deployment: false if: github.event.action == 'closed' runs-on: ubuntu-latest permissions: @@ -557,5 +560,5 @@ jobs: - name: Cleanup temporary files if: always() run: | - rm -f ../private.key + rm -f ../private.key docker-compose.yml storybook.tgz continue-on-error: true diff --git a/.github/workflows/PR-Demo-cleanup.yml b/.github/workflows/PR-Demo-cleanup.yml index 1407939994..098f8d7803 100644 --- a/.github/workflows/PR-Demo-cleanup.yml +++ b/.github/workflows/PR-Demo-cleanup.yml @@ -9,7 +9,10 @@ permissions: jobs: cleanup: - environment: pr-preview + # Tearing a preview down is not a deployment - no deployment object. + environment: + name: pr-preview + deployment: false if: github.event.action == 'closed' runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/ai-engine.yml b/.github/workflows/ai-engine.yml index 015934030f..b8e15466e5 100644 --- a/.github/workflows/ai-engine.yml +++ b/.github/workflows/ai-engine.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/backend-build.yml b/.github/workflows/backend-build.yml index 6623940bce..e9a27a072d 100644 --- a/.github/workflows/backend-build.yml +++ b/.github/workflows/backend-build.yml @@ -20,7 +20,9 @@ permissions: jobs: build: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false runs-on: ubuntu-latest strategy: fail-fast: false @@ -195,7 +197,7 @@ jobs: - name: Install uv if: always() && matrix.flavor == 'saas' - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/build-enterprise.yml b/.github/workflows/build-enterprise.yml index b4a8373ccc..0bd376766e 100644 --- a/.github/workflows/build-enterprise.yml +++ b/.github/workflows/build-enterprise.yml @@ -42,7 +42,9 @@ jobs: uses: ./.github/workflows/_runner-pick.yml playwright-e2e-enterprise: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false needs: pick # Skip on fork PRs / untrusted authors: they have no PREMIUM_KEY_ENTERPRISE, # so the suite can't boot premium and would fail. See the header comment. @@ -322,10 +324,18 @@ jobs: path: frontend/playwright-report/ retention-days: 7 + - name: Cleanup temporary files + if: always() + run: | + rm -f /tmp/helpers.sh /tmp/backend.log /tmp/backend.pid + continue-on-error: true + # Multi-node regression: builds + seeds the clustered stack (testing/compose/docker-compose-multinode.yml) # and runs behave features/multinode. Licence-gated, so it runs after the Playwright job (not in parallel). multinode-e2e: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false needs: [pick, playwright-e2e-enterprise] # Nightly cron + manual dispatch only (heavy build), fork-gated for the licence secret. if: >- @@ -347,7 +357,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 566262d24f..d35b2ee177 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,6 +37,7 @@ jobs: timeout-minutes: 3 outputs: build: ${{ steps.changes.outputs.build }} + backend: ${{ steps.changes.outputs.backend }} project: ${{ steps.changes.outputs.project }} openapi: ${{ steps.changes.outputs.openapi }} frontend: ${{ steps.changes.outputs.frontend }} @@ -61,61 +62,12 @@ jobs: filters: .github/config/.files.yaml gradle-cache-prime: - environment: ci-unsigned - name: Prime shared Gradle cache needs: [files-changed] - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 - with: - egress-policy: audit - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Calculate Gradle cache key - id: gradle-cache-key - shell: bash - run: | - echo "key=gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}" >> "$GITHUB_OUTPUT" - - - name: Cache Gradle (lookup-only) - id: cache-gradle-restore - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ steps.gradle-cache-key.outputs.key }} - lookup-only: true - - - name: Set up JDK 25 - if: steps.cache-gradle-restore.outputs.cache-hit != 'true' - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 - with: - java-version: "25" - distribution: "temurin" - - - name: Resolve backend dependencies - if: steps.cache-gradle-restore.outputs.cache-hit != 'true' - run: ./gradlew :stirling-pdf:classes --no-daemon - env: - STIRLING_FLAVOR: saas - MAVEN_USER: ${{ secrets.MAVEN_USER }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} - - - name: Save cache Gradle User Home - if: steps.cache-gradle-restore.outputs.cache-hit != 'true' - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ steps.gradle-cache-key.outputs.key }} + uses: ./.github/workflows/gradle-cache-prime.yml + secrets: inherit build: + if: needs.files-changed.outputs.backend == 'true' needs: [files-changed, gradle-cache-prime] permissions: actions: read @@ -194,7 +146,7 @@ jobs: check-licence: if: needs.files-changed.outputs.build == 'true' - needs: [files-changed, build, gradle-cache-prime] + needs: [files-changed, gradle-cache-prime] permissions: contents: read uses: ./.github/workflows/check-licence.yml @@ -213,7 +165,14 @@ jobs: docker-base-changed: ${{ needs.files-changed.outputs.docker-base }} test-build-docker-images: - if: github.event_name == 'pull_request' && needs.files-changed.outputs.project == 'true' + if: | + always() && + github.event_name == 'pull_request' && + needs.files-changed.outputs.project == 'true' && + contains(fromJSON('["success", "skipped"]'), needs.gradle-cache-prime.result) && + contains(fromJSON('["success", "skipped"]'), needs.build.result) && + contains(fromJSON('["success", "skipped"]'), needs.check-generateOpenApiDocs.result) && + contains(fromJSON('["success", "skipped"]'), needs.check-licence.result) needs: [ files-changed, @@ -319,6 +278,7 @@ jobs: if: always() needs: - files-changed + - gradle-cache-prime - build - db-migration-test - check-generateOpenApiDocs @@ -346,6 +306,7 @@ jobs: env: RESULTS: | files-changed=${{ needs.files-changed.result }} + gradle-cache-prime=${{ needs.gradle-cache-prime.result }} build=${{ needs.build.result }} db-migration-test=${{ needs.db-migration-test.result }} check-generateOpenApiDocs=${{ needs.check-generateOpenApiDocs.result }} diff --git a/.github/workflows/check-generated-models.yml b/.github/workflows/check-generated-models.yml index fafffcc241..4428704b10 100644 --- a/.github/workflows/check-generated-models.yml +++ b/.github/workflows/check-generated-models.yml @@ -36,7 +36,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/check-licence.yml b/.github/workflows/check-licence.yml index 17c64d5c64..4e04a83656 100644 --- a/.github/workflows/check-licence.yml +++ b/.github/workflows/check-licence.yml @@ -10,7 +10,9 @@ permissions: jobs: check-licence: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false runs-on: ubuntu-latest steps: - name: Harden Runner diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index ed83447335..bc9b302857 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -11,7 +11,9 @@ permissions: jobs: check-generate-openapi-docs: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false runs-on: ubuntu-latest steps: - name: Harden Runner diff --git a/.github/workflows/check_toml.yml b/.github/workflows/check_toml.yml index 1681546e71..9ccfc4af52 100644 --- a/.github/workflows/check_toml.yml +++ b/.github/workflows/check_toml.yml @@ -195,7 +195,7 @@ jobs: core.exportVariable("REFERENCE_FILE", referenceFilePath); - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/coverage-aggregate.yml b/.github/workflows/coverage-aggregate.yml index 61ef8793c4..87fbb24eac 100644 --- a/.github/workflows/coverage-aggregate.yml +++ b/.github/workflows/coverage-aggregate.yml @@ -55,7 +55,7 @@ jobs: distribution: "temurin" - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/db-migration-test.yml b/.github/workflows/db-migration-test.yml index ccb46d3988..7176163440 100644 --- a/.github/workflows/db-migration-test.yml +++ b/.github/workflows/db-migration-test.yml @@ -13,7 +13,9 @@ permissions: jobs: migration-test: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -79,3 +81,8 @@ jobs: path: /tmp/stirling-migration-failed-*/app.log retention-days: 7 if-no-files-found: warn + + - name: Cleanup temporary files + if: always() + run: rm -rf /tmp/stirling-migration-failed-* + continue-on-error: true diff --git a/.github/workflows/docker-compose-tests.yml b/.github/workflows/docker-compose-tests.yml index 039c73e5db..54f2e6b5f6 100644 --- a/.github/workflows/docker-compose-tests.yml +++ b/.github/workflows/docker-compose-tests.yml @@ -17,7 +17,9 @@ permissions: jobs: docker-compose-tests: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false runs-on: ubuntu-latest permissions: actions: write @@ -64,11 +66,11 @@ jobs: - name: Install Docker Compose run: | - sudo curl -SL "https://github.com/docker/compose/releases/download/v2.39.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo curl -SL "https://github.com/docker/compose/releases/download/v5.4.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/e2e-live.yml b/.github/workflows/e2e-live.yml index 43d66dd1cf..29668940c4 100644 --- a/.github/workflows/e2e-live.yml +++ b/.github/workflows/e2e-live.yml @@ -11,7 +11,9 @@ permissions: jobs: playwright-e2e-live: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -104,7 +106,7 @@ jobs: fi - name: Install uv if: always() - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/e2e-stubbed.yml b/.github/workflows/e2e-stubbed.yml index d02005936c..b62c3e72ec 100644 --- a/.github/workflows/e2e-stubbed.yml +++ b/.github/workflows/e2e-stubbed.yml @@ -14,6 +14,11 @@ jobs: playwright-e2e: name: playwright-e2e (${{ matrix.browser }}) runs-on: ubuntu-latest + # The image already contains the Playwright browsers and all Linux + # dependencies. This keeps the matrix for per-browser reporting while + # avoiding three concurrent `playwright install --with-deps` runs. + container: + image: mcr.microsoft.com/playwright:v1.58.2-noble@sha256:6446946a1d9fd62d9ae501312a2d76a43ee688542b21622056a372959b65d63d strategy: # One browser breaking must not mask a failure in another - report all. fail-fast: false @@ -40,15 +45,23 @@ jobs: cache-dependency-path: frontend/package-lock.json - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 - - name: Install Playwright (${{ matrix.browser }}) - run: task e2e:install -- ${{ matrix.browser }} - name: Build frontend (production bundle for vite preview) env: VITE_BUILD_FOR_PREVIEW: "1" run: task frontend:build - name: Run stubbed E2E tests (${{ matrix.browser }}) env: + # The official Playwright image expects its browser runtime under + # the root home directory. Keep this scoped to Playwright and use a + # neutral Docker config path so Docker does not read /root/.docker. + HOME: /root + DOCKER_CONFIG: /tmp/playwright-docker-config PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json + NPM_CONFIG_PREFER_OFFLINE: "true" + NPM_CONFIG_FETCH_RETRIES: "5" + NPM_CONFIG_FETCH_RETRY_FACTOR: "2" + NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "1000" + NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "120000" run: task e2e:stubbed-project PROJECT=${{ matrix.project }} -- --workers=3 - name: Flag flaky tests # Runs regardless of the test outcome: a flaky test (passed on retry) diff --git a/.github/workflows/frontend-backend-licenses-update.yml b/.github/workflows/frontend-backend-licenses-update.yml index 7fd0136718..9aef2316d2 100644 --- a/.github/workflows/frontend-backend-licenses-update.yml +++ b/.github/workflows/frontend-backend-licenses-update.yml @@ -43,7 +43,9 @@ jobs: generate-frontend-license-report: # ci-bot, not bot-identity: this job runs on PRs too, and bot-identity is main-only. - environment: ci-bot + environment: + name: ci-bot + deployment: false if: needs.files-changed.outputs.licenses-frontend == 'true' name: Generate Frontend License Report needs: files-changed @@ -319,7 +321,9 @@ jobs: generate-backend-license-report: # ci-bot, not bot-identity: this job runs on PRs too, and bot-identity is main-only. - environment: ci-bot + environment: + name: ci-bot + deployment: false if: needs.files-changed.outputs.licenses-backend == 'true' needs: files-changed name: Generate Backend License Report diff --git a/.github/workflows/frontend-validation.yml b/.github/workflows/frontend-validation.yml index 133d940b28..0cad599a1c 100644 --- a/.github/workflows/frontend-validation.yml +++ b/.github/workflows/frontend-validation.yml @@ -107,7 +107,7 @@ jobs: } - name: Install uv if: always() - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/gradle-cache-prime.yml b/.github/workflows/gradle-cache-prime.yml new file mode 100644 index 0000000000..b3700d8ac0 --- /dev/null +++ b/.github/workflows/gradle-cache-prime.yml @@ -0,0 +1,66 @@ +name: Prime Gradle Cache + +on: + workflow_call: + push: + branches: ["main"] + +permissions: + contents: read + +jobs: + gradle-cache-prime: + environment: + name: ci-unsigned + deployment: false + name: Prime shared Gradle cache + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 + with: + egress-policy: audit + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Calculate Gradle cache key + id: gradle-cache-key + shell: bash + run: | + echo "key=gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}" >> "$GITHUB_OUTPUT" + + - name: Cache Gradle (lookup-only) + id: cache-gradle-restore + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ steps.gradle-cache-key.outputs.key }} + lookup-only: true + + - name: Set up JDK 25 + if: steps.cache-gradle-restore.outputs.cache-hit != 'true' + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + with: + java-version: "25" + distribution: "temurin" + + - name: Resolve backend dependencies + if: steps.cache-gradle-restore.outputs.cache-hit != 'true' + run: ./gradlew :stirling-pdf:classes --no-daemon + env: + STIRLING_FLAVOR: saas + MAVEN_USER: ${{ secrets.MAVEN_USER }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} + + - name: Save cache Gradle User Home + if: steps.cache-gradle-restore.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ steps.gradle-cache-key.outputs.key }} diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml index 0f0b2d3585..8e1c47b412 100644 --- a/.github/workflows/multiOSReleases.yml +++ b/.github/workflows/multiOSReleases.yml @@ -38,7 +38,9 @@ permissions: jobs: determine-matrix: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest outputs: @@ -116,7 +118,9 @@ jobs: env: INPUT_PLATFORM: ${{ github.event.inputs.platform }} build-jars: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false needs: determine-matrix runs-on: ubuntu-latest strategy: @@ -693,6 +697,17 @@ jobs: path: ./dist/* retention-days: 1 + - name: Cleanup temporary files + if: always() + shell: bash + run: | + rm -f certificate.p12 + rm -rf "$RUNNER_TEMP/msi-verify" + if [ "${{ matrix.platform }}" = "macos-15" ]; then + security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true + fi + continue-on-error: true + collect-and-release: needs: [determine-matrix, build, build-jars] runs-on: ubuntu-latest @@ -857,7 +872,7 @@ jobs: # Gate publish on valid updater sigs. Runs after the review upload (so # artifacts survive for debugging) and before action-gh-release. - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index c92d17f027..42898c4b43 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -127,7 +127,9 @@ jobs: # Runs the @nightly tag (conversion scenarios) plus a 10-shard concurrency run # of every other feature. cucumber-nightly: - environment: ci-unsigned + environment: + name: ci-unsigned + deployment: false name: Cucumber (nightly scenarios + full concurrency) runs-on: ubuntu-latest # Fork pull requests get no MAVEN_* secrets, so the image build cannot work. @@ -146,13 +148,13 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up JDK 25 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: java-version: "25" distribution: "temurin" - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/pre_commit.yml b/.github/workflows/pre_commit.yml index db67ccefe6..d4271c6aad 100644 --- a/.github/workflows/pre_commit.yml +++ b/.github/workflows/pre_commit.yml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index f0129277de..f5b9447720 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -75,6 +75,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_files_v2.yml b/.github/workflows/sync_files_v2.yml index 124c992c71..6b436ed2f6 100644 --- a/.github/workflows/sync_files_v2.yml +++ b/.github/workflows/sync_files_v2.yml @@ -53,7 +53,7 @@ jobs: private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index e3f3122773..4e9eddd614 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -63,7 +63,9 @@ jobs: determine-matrix: # Only probes APPLE_CERTIFICATE for presence, so it stays on the unrestricted # signing environment - release-signing would block every PR run. - environment: ci-signing + environment: + name: ci-signing + deployment: false if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest outputs: @@ -675,6 +677,17 @@ jobs: fi done + - name: Cleanup temporary files + if: always() + shell: bash + run: | + rm -f certificate.p12 + rm -rf "$RUNNER_TEMP/msi-verify" + if [ "${{ matrix.platform }}" = "macos-15" ]; then + security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true + fi + continue-on-error: true + pr-comment: needs: build runs-on: ubuntu-latest diff --git a/.github/workflows/test-build-docker.yml b/.github/workflows/test-build-docker.yml index 12d5a35a1f..dfdd0e6c62 100644 --- a/.github/workflows/test-build-docker.yml +++ b/.github/workflows/test-build-docker.yml @@ -22,22 +22,45 @@ permissions: contents: read jobs: - # TODO: extract a pre-matrix `prepare` job that runs once and produces - # shared artifacts for the three matrix entries below to consume: - # 1. `task backend:build` — currently runs 3× in parallel with - # identical env (DISABLE_ADDITIONAL_FEATURES=true, - # STIRLING_PDF_DESKTOP_UI=false). Build once, upload the JAR as an - # artifact, matrix entries download. - # 2. The base-image `docker build` (gated on docker-base-changed) — - # currently runs 3× in parallel against the same Dockerfile and - # context. Build once, `docker save` to an artifact, matrix entries - # `docker load` before the embedded build. - # Saves ~2 full backend builds + 2 base-image builds per PR that touches - # docker. May also be reusable from backend-build.yml's jdk-25 + - # spring-security=true matrix entry if `task backend:build` and - # `task backend:build:ci` produce equivalent JARs (verify before wiring). + # A changed base image is shared by all three embedded-image builds. Build + # it once and transfer it as an artifact; the matrix jobs use the local + # Docker driver so the loaded image is visible to the build. + prepare-base-image: + if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true' + environment: + name: ci-unsigned + deployment: false + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Harden Runner + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 + with: + egress-policy: audit + + - name: Checkout Repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build base image locally + run: docker build --platform linux/amd64 -t stirling-pdf-base:pr-test -f docker/base/Dockerfile docker/base + + - name: Export base image + run: docker save stirling-pdf-base:pr-test | gzip -1 > stirling-pdf-base-pr-test.tar.gz + + - name: Upload base image + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: docker-base-pr-test + path: stirling-pdf-base-pr-test.tar.gz + retention-days: 1 + if-no-files-found: error + test-build-docker-images: - environment: ci-unsigned + if: always() && (needs.prepare-base-image.result == 'success' || needs.prepare-base-image.result == 'skipped') + needs: [prepare-base-image] + environment: + name: ci-unsigned + deployment: false runs-on: ubuntu-latest strategy: fail-fast: false @@ -79,6 +102,16 @@ jobs: docker system prune -af || true echo "Disk space after cleanup:" && df -h + - name: Download prepared base image + if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: docker-base-pr-test + + - name: Load prepared base image + if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true' + run: gzip -dc stirling-pdf-base-pr-test.tar.gz | docker load + - name: Restore cache Gradle User Home uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -111,11 +144,6 @@ jobs: id: buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - - name: Build base image locally (PR base change only) - if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true' - run: | - docker build -t stirling-pdf-base:pr-test -f docker/base/Dockerfile docker/base - - name: Set base image and platform for this build id: build-params # Pass workflow inputs through env vars rather than expanding `${{ }}` diff --git a/.github/workflows/update-gradle.yml b/.github/workflows/update-gradle.yml new file mode 100644 index 0000000000..72b347e62f --- /dev/null +++ b/.github/workflows/update-gradle.yml @@ -0,0 +1,112 @@ +name: Update Gradle + +on: + workflow_dispatch: + schedule: + - cron: "0 3 * * 1" + +concurrency: + group: update-gradle + cancel-in-progress: true + +jobs: + update-gradle: + name: Update Gradle and Docker images + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Java + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + with: + distribution: temurin + java-version: "25" + + - name: Find latest Gradle release + id: gradle + shell: bash + run: | + set -euo pipefail + version=$(curl --fail --silent --show-error --retry 3 \ + https://services.gradle.org/versions/current | jq -r '.version') + [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { + echo "Could not determine a stable Gradle version: $version" >&2 + exit 1 + } + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Find matching Docker image digest + id: docker + env: + GRADLE_VERSION: ${{ steps.gradle.outputs.version }} + shell: bash + run: | + set -euo pipefail + tag="${GRADLE_VERSION}-jdk25" + digest=$(curl --fail --silent --show-error --retry 3 \ + "https://hub.docker.com/v2/repositories/library/gradle/tags/${tag}" \ + | jq -r '.digest // empty') + [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || { + echo "Docker image gradle:${tag} was not found" >&2 + exit 1 + } + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "digest=$digest" >> "$GITHUB_OUTPUT" + + - name: Update Gradle wrapper + env: + GRADLE_VERSION: ${{ steps.gradle.outputs.version }} + run: ./gradlew wrapper --gradle-version "$GRADLE_VERSION" --distribution-type bin + + - name: Update Gradle Docker images + env: + DOCKER_TAG: ${{ steps.docker.outputs.tag }} + DOCKER_DIGEST: ${{ steps.docker.outputs.digest }} + shell: bash + run: | + set -euo pipefail + find docker -type f -name 'Dockerfile*' -print0 | + xargs -0 sed -E -i \ + "s#gradle:[^@[:space:]]+-jdk25(@sha256:[^[:space:]]+)?#gradle:${DOCKER_TAG}@${DOCKER_DIGEST}#g" + + - name: Verify Gradle update + env: + EXPECTED_VERSION: ${{ steps.gradle.outputs.version }} + shell: bash + run: | + set -euo pipefail + actual=$(./gradlew --version | sed -n 's/^Gradle \([0-9.]*\)$/\1/p') + [[ "$actual" == "$EXPECTED_VERSION" ]] || { + echo "Wrapper resolved Gradle $actual, expected $EXPECTED_VERSION" >&2 + exit 1 + } + if git diff --quiet; then + echo "Gradle is already up to date." + exit 0 + fi + git diff --check + + - name: Create pull request + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: automation/update-gradle + delete-branch: true + commit-message: "chore: update Gradle" + title: "chore: update Gradle to ${{ steps.gradle.outputs.version }}" + body: | + Automated update of the Gradle wrapper and Gradle Docker build images. + + Gradle version: `${{ steps.gradle.outputs.version }}` + Docker image: `gradle:${{ steps.docker.outputs.tag }}` + labels: dependencies diff --git a/.gitleaksignore b/.gitleaksignore index 12d98aebeb..c3917e985f 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -27,3 +27,8 @@ app/core/src/main/java/stirling/software/SPDF/pdf/signature/CreateSignatureBase. # Supabase publishable key (public by design, RLS-protected) used as a CI fallback # default in the tauri-build workflow when the GitHub secret is unset - not a real secret. .github/workflows/tauri-build.yml:generic-api-key:402 + +# Staging Supabase publishable key (public by design). Ignored here rather than with an +# inline gitleaks:allow because a trailing comment in a .properties file is part of the +# value, so the pragma would end up inside the key. +app/saas/src/main/resources/application-staging.properties:generic-api-key:16 diff --git a/.imgbotconfig b/.imgbotconfig index 720b938475..6a1b1bf7a2 100644 --- a/.imgbotconfig +++ b/.imgbotconfig @@ -1,5 +1,7 @@ { "ignoredFiles": [ - "frontend/editor/src-tauri/icons/icon.png" + "frontend/editor/src-tauri/icons/macos/*", + "frontend/editor/src-tauri/icons/linux/*", + "frontend/editor/src-tauri/icons/windows/*" ] } diff --git a/.taskfiles/backend.yml b/.taskfiles/backend.yml index 63773f61fc..08a12b9535 100644 --- a/.taskfiles/backend.yml +++ b/.taskfiles/backend.yml @@ -57,16 +57,57 @@ tasks: - cmd: ./gradlew clean bootRun -PbuildWithFrontend=true platforms: [linux, darwin] + # SaaS backend. dev:saas -> the PR's preview branch, staging:saas -> shared v3, + # PROFILES=none -> production against your own SAAS_DB_*. Production has no named + # task on purpose. Use `none`, not an empty value: Go template `default` treats "" + # as absent and would resolve back to dev. + dev:saas: - desc: "Start backend in SaaS flavor against Supabase" - # `dotenv:` reads from the root Taskfile's directory (".") because this - # subtaskfile is included with `dir: .`. + desc: "Start SaaS backend against the current PR's Supabase preview branch" + dotenv: ['app/.env.saas.local', 'app/.env.saas'] + vars: + PROFILES: '{{.PROFILES | default "dev"}}' + cmds: + # Don't move this check into a `sh:` var: dotenv is visible in cmds but not + # during var evaluation, so the test would always see an empty value. + - cmd: | + if [ "{{.PROFILES}}" = "dev" ] && [ -z "${SAAS_DEV_PROJECT_REF:-}" ]; then + echo ">> SAAS_DEV_PROJECT_REF is not set." + echo ">> Testing a SaaS PR? Put its ref, DB password and publishable key in app/.env.saas.local." + echo ">> Wanted the shared v3 project? Use 'task backend:staging:saas' instead." + exit 1 + fi + - task: _run:saas + vars: + PORT: '{{.PORT}}' + PROFILES: '{{.PROFILES}}' + AIENGINE_URL: '{{.AIENGINE_URL}}' + AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}' + AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}' + + staging:saas: + desc: "Start SaaS backend against the shared v3 staging project" + cmds: + - task: _run:saas + vars: + PORT: '{{.PORT}}' + PROFILES: staging + AIENGINE_URL: '{{.AIENGINE_URL}}' + AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}' + AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}' + + _run:saas: + internal: true dotenv: ['app/.env.saas.local', 'app/.env.saas'] ignore_error: true vars: PORT: '{{.PORT | default "8080"}}' - # Override to "" to run the pure `saas` profile against your own SAAS_DB_*. PROFILES: '{{.PROFILES | default "dev"}}' + # Built here rather than inline in the cmds below: the Windows line is an + # unquoted YAML scalar wrapping a cmd.exe string, so a nested {{if ne .X + # "none"}} needs escaped quotes that reach the Go template as literal + # backslashes and fail with `unexpected "\" in operand`. + PROFILE_ARGS: '{{if ne .PROFILES "none"}}--spring.profiles.include={{.PROFILES}}{{end}}' AIENGINE_URL: '{{.AIENGINE_URL | default ""}}' AIENGINE_ENABLED: '{{.AIENGINE_ENABLED | default "false"}}' AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS | default "120"}}' @@ -77,9 +118,11 @@ tasks: AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}' AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}' cmds: - - cmd: cmd /c ".\gradlew.bat :stirling-pdf:bootRun {{if .PROFILES}}--args=\"--spring.profiles.include={{.PROFILES}}\"{{end}}" + # PROFILE_ARGS is empty when PROFILES=none, i.e. the bare `saas` profile + # against SAAS_DB_* (production). + - cmd: cmd /c ".\gradlew.bat :stirling-pdf:bootRun {{if .PROFILE_ARGS}}--args=\"{{.PROFILE_ARGS}}\"{{end}}" platforms: [windows] - - cmd: ./gradlew :stirling-pdf:bootRun {{if .PROFILES}}--args='--spring.profiles.include={{.PROFILES}}'{{end}} + - cmd: ./gradlew :stirling-pdf:bootRun {{if .PROFILE_ARGS}}--args='{{.PROFILE_ARGS}}'{{end}} platforms: [linux, darwin] build: diff --git a/.taskfiles/frontend.yml b/.taskfiles/frontend.yml index 1f369b9709..b8af2e40c9 100644 --- a/.taskfiles/frontend.yml +++ b/.taskfiles/frontend.yml @@ -5,6 +5,14 @@ version: '3' # mode flag) or use `--project editor/...` for tsc — so the editor lives # under frontend/editor/ without each task needing a cd. +vars: + # Dev-only browser-tab label so concurrent worktrees are distinguishable. Only + # the worktree folder basename (e.g. "wt1") is exposed — never the full path, + # hostname, or user. Dropped from production builds. + DEV_LABEL: + sh: >- + {{if eq OS "windows"}}powershell -NoProfile -Command '$root = git rev-parse --show-toplevel 2>$null; if (-not $root) { $root = (Get-Location).Path }; Split-Path -Leaf $root'{{else}}basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"{{end}} + tasks: install: desc: "Install dependencies" @@ -80,16 +88,52 @@ tasks: OPEN: '{{.OPEN | default ""}}' env: BACKEND_URL: '{{.BACKEND_URL}}' - # Dev-only browser-tab label so concurrent worktrees are distinguishable. - # Only the worktree folder basename (e.g. "wt1") is exposed — never the - # full path, hostname, or user. Consumed at dev-serve time by vite.config - # and dropped from production builds. - STIRLING_DEV_LABEL: - sh: >- - {{if eq OS "windows"}}powershell -NoProfile -Command '$root = git rev-parse --show-toplevel 2>$null; if (-not $root) { $root = (Get-Location).Path }; Split-Path -Leaf $root'{{else}}basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"{{end}} + STIRLING_DEV_LABEL: '{{.DEV_LABEL}}' cmds: - npx vite editor --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}} + # Separate from dev:_run rather than a flag on it: Task sets an `env:` key even + # when its value resolves to empty, and Vite treats an empty process.env VITE_* as + # authoritative over the committed editor/.env, so folding these in blanks Supabase + # config for the core, proprietary and desktop dev servers. + dev:_run:saas: + internal: true + ignore_error: true + # The backend's own env files, so both halves target one project. Paths are + # relative to this taskfile's dir, `frontend`. + dotenv: ['../app/.env.saas.local', '../app/.env.saas'] + vars: + PORT: '{{.PORT | default "5173"}}' + BACKEND_URL: '{{.BACKEND_URL | default "http://localhost:8080"}}' + OPEN: '{{.OPEN | default ""}}' + SAAS_ENV: '{{.SAAS_ENV | default "dev"}}' + env: + BACKEND_URL: '{{.BACKEND_URL}}' + STIRLING_DEV_LABEL: '{{.DEV_LABEL}}' + SAAS_ENV: '{{.SAAS_ENV}}' + # A real process.env VITE_* beats a committed .env in Vite (loadEnv applies + # process.env last), which is what lets this override editor/.env. + # + # These must stay `sh:`, not Go templates: dotenv values are visible to Task's + # embedded shell but not to templates, where {{.SAAS_DEV_PROJECT_REF}} is + # always empty. + VITE_SUPABASE_URL: + sh: | + case "${SAAS_ENV:-dev}" in + staging) ref="${SAAS_STAGING_PROJECT_REF:?set it in app/.env.saas.local}" ;; + *) ref="${SAAS_DEV_PROJECT_REF:?set it in app/.env.saas.local, or run task staging:saas}" ;; + esac + echo "https://${ref}.supabase.co" + VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY: + sh: | + case "${SAAS_ENV:-dev}" in + staging) echo "${SAAS_STAGING_PUBLISHABLE_KEY:?set it in app/.env.saas.local}" ;; + *) echo "${SAAS_DEV_PUBLISHABLE_KEY:?set it in app/.env.saas.local}" ;; + esac + cmds: + - 'echo ">> frontend Supabase target: $VITE_SUPABASE_URL"' + - npx vite editor --mode saas --port {{.PORT}}{{if .OPEN}} --open{{end}} + dev: desc: "Start frontend dev server" cmds: @@ -111,13 +155,23 @@ tasks: vars: { MODE: proprietary, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' } dev:saas: - desc: "Start frontend dev server in SaaS mode" + desc: "Start frontend dev server in SaaS mode (SAAS_ENV=dev|staging|prod)" deps: - task: prepare vars: { MODE: saas } + vars: + SAAS_ENV: '{{.SAAS_ENV | default "dev"}}' + # prod routes to the plain runner, which sets no VITE_SUPABASE_* and so leaves + # the committed editor/.env alone. + RUNNER: '{{if eq .SAAS_ENV "prod"}}dev:_run{{else}}dev:_run:saas{{end}}' cmds: - - task: dev:_run - vars: { MODE: saas, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' } + - task: '{{.RUNNER}}' + vars: + MODE: saas + PORT: '{{.PORT}}' + BACKEND_URL: '{{.BACKEND_URL}}' + OPEN: '{{.OPEN}}' + SAAS_ENV: '{{.SAAS_ENV}}' dev:desktop: desc: "Start frontend dev server in desktop mode" diff --git a/DeveloperGuide.md b/DeveloperGuide.md index 7c2e21d5ca..d87406715b 100644 --- a/DeveloperGuide.md +++ b/DeveloperGuide.md @@ -46,8 +46,8 @@ This guide focuses on developing for Stirling 2.0, including both the React fron - Docker - Git - Java JDK 25 -- Node.js 18+ and npm (required for frontend development) -- Gradle 7.0 or later (Included within the repo) +- Node.js 22+ and npm (required for frontend development) +- Gradle 9.0 or later (Included within the repo) - [uv](https://docs.astral.sh/uv/) — Python package manager (required for engine development) - Rust and Cargo (required for Tauri desktop app development) - Tauri CLI (install with `cargo install tauri-cli`) diff --git a/Taskfile.yml b/Taskfile.yml index fc7a564032..92dcdcc742 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -99,11 +99,22 @@ tasks: BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' OPEN: "true" + # Set SAAS_DEV_PROJECT_REF in app/.env.saas.local to pick the PR. dev:saas: - desc: "Start SaaS backend + frontend concurrently on free ports" + desc: "Start SaaS backend + frontend + engine against the current PR's preview branch" cmds: - task: dev:_all - vars: { FRONTEND: saas, BACKEND: saas } + vars: { FRONTEND: saas, BACKEND: saas, SAAS_ENV: dev } + + staging:saas: + desc: "Start SaaS backend + frontend + engine against the shared v3 staging project" + cmds: + - task: dev:_all + vars: + FRONTEND: saas + BACKEND: saas + BACKEND_TASK: backend:staging:saas + SAAS_ENV: staging dev:all: desc: "Start backend + frontend + engine concurrently on free ports" @@ -115,6 +126,9 @@ tasks: vars: FRONTEND: '{{.FRONTEND | default "proprietary"}}' BACKEND: '{{.BACKEND | default "proprietary"}}' + BACKEND_TASK: '{{.BACKEND_TASK | default (printf "backend:dev:%s" .BACKEND)}}' + # Only meaningful to the saas frontend; every other flavor ignores it. + SAAS_ENV: '{{.SAAS_ENV | default ""}}' PORTS: sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173 5001{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5001{{end}}' BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}' @@ -124,7 +138,7 @@ tasks: - task: engine:dev vars: PORT: '{{.ENGINE_PORT}}' - - task: 'backend:dev:{{.BACKEND}}' + - task: '{{.BACKEND_TASK}}' vars: PORT: '{{.BACKEND_PORT}}' AIENGINE_URL: 'http://localhost:{{.ENGINE_PORT}}' @@ -134,6 +148,7 @@ tasks: PORT: '{{.FRONTEND_PORT}}' BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' OPEN: "true" + SAAS_ENV: '{{.SAAS_ENV}}' # ============================================================ # Build diff --git a/app/.env.saas b/app/.env.saas index fb5feec559..25eefb84c5 100644 --- a/app/.env.saas +++ b/app/.env.saas @@ -1,15 +1,16 @@ -############################################################################### -# Stirling-PDF SaaS environment defaults. +# Stirling-PDF SaaS environment defaults. Committed, non-secret. Real values for secrets go in +# .env.saas.local, which is loaded first and wins. Do not commit that file. # -# This file is committed and provides non-secret defaults loaded by -# `task backend:dev:saas`. Put real values for secrets (passwords, project -# refs, edge function secrets) in `.env.saas.local` - any variable set there -# takes precedence over what's defined here. +# Three environments, each deriving its Supabase URLs, JWT issuer and JWKS from one project ref: # -# DO NOT commit `.env.saas.local`. Only `.env.saas` is checked in. -############################################################################### +# prod PROFILES=none SAAS_DB_* the live project +# staging PROFILES=staging SAAS_STAGING_* pinned to v3, always there +# dev PROFILES=dev SAAS_DEV_* follows a SaaS PR's preview branch +# +# dev is the default for `task backend:dev:saas`. Use staging for somewhere stable; use dev when +# testing an open SaaS PR, since its preview branch is the only place those migrations are applied. -# ---------- Supabase project ---------- +# ---------- Supabase project (prod / no-profile) ---------- # Project reference (the subdomain part of .supabase.co). Required. # Set in .env.saas.local. SAAS_DB_PROJECT_REF= @@ -17,18 +18,35 @@ SAAS_DB_PROJECT_REF= # Edge function secret used by billing/license rollup calls. Set in .env.saas.local. SUPABASE_EDGE_FUNCTION_SECRET= -# ---------- Database (saas profile) ---------- -# Direct JDBC URL to the Supabase Postgres. Required when running the plain -# `saas` profile (i.e. without `--spring.profiles.include=dev`). +# ---------- Database (no profile) ---------- +# Direct JDBC URL to the Supabase Postgres. Required when running without +# `--spring.profiles.include=...`. # Example: jdbc:postgresql://db..supabase.co:5432/postgres SAAS_DB_URL= SAAS_DB_USERNAME=postgres SAAS_DB_PASSWORD= -# ---------- Database (dev profile overrides) ---------- -# Used when `--spring.profiles.include=dev` is active. The dev profile -# defaults the URL/username to the shared dev Supabase project, but the -# password must still be provided in .env.saas.local. -SAAS_DEV_DB_URL= +# ---------- staging profile ---------- +# The shared long-lived v3 project. application-staging.properties defaults the ref, +# URL, database host and meter endpoint, so staging needs only the password, in +# .env.saas.local. Set SAAS_STAGING_PROJECT_REF to repoint it; everything derives. +# +# The ref and publishable key are duplicated here because the task derives the +# frontend's VITE_SUPABASE_* from them and a shell cannot read a Spring default. +# Neither is secret: the ref is a public subdomain, the key ships in the bundle. +SAAS_STAGING_PROJECT_REF=qacaivhsjtftfwtgjvva +SAAS_STAGING_PUBLISHABLE_KEY=sb_publishable_nIM8y-9ARPE7EzQwAQHKMg_40fCN6kY # gitleaks:allow +SAAS_STAGING_DB_USERNAME=postgres +SAAS_STAGING_DB_PASSWORD= + +# ---------- dev profile ---------- +# The SaaS PR's Supabase preview branch. Take the ref from that PR's "Supabase +# Preview" check; the profile derives URL, JWT issuer, JWKS, meter endpoint and +# database host from it, so this one value follows a different PR. +# +# A preview branch has its own password and keys; the parent project's will not +# authenticate. Both go in .env.saas.local, along with the ref. +SAAS_DEV_PROJECT_REF= +SAAS_DEV_PUBLISHABLE_KEY= SAAS_DEV_DB_USERNAME=postgres SAAS_DEV_DB_PASSWORD= diff --git a/app/common/build.gradle b/app/common/build.gradle index f53ad0eb79..bb956504cc 100644 --- a/app/common/build.gradle +++ b/app/common/build.gradle @@ -21,8 +21,8 @@ dependencies { api 'org.snakeyaml:snakeyaml-engine:3.0.1' api "org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.3" // Simple Java Mail for EML/MSG parsing (replaces direct Angus Mail usage) - api 'org.simplejavamail:simple-java-mail:9.2.0' - api 'org.simplejavamail:outlook-module:9.2.0' // MSG file support + api 'org.simplejavamail:simple-java-mail:9.3.2' + api 'org.simplejavamail:outlook-module:9.3.2' // MSG file support api 'jakarta.mail:jakarta.mail-api:2.1.5' runtimeOnly 'org.eclipse.angus:angus-mail:2.0.5' 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 ff1a880010..5e8f7fe336 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 @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @@ -13,6 +14,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import stirling.software.common.model.ApplicationProperties; +import stirling.software.common.service.PdfaLevelAServiceInterface; @Service @Slf4j @@ -51,12 +53,16 @@ public class EndpointConfiguration { private Map groupDisableReasons = new ConcurrentHashMap<>(); private Map> endpointAlternatives = new ConcurrentHashMap<>(); private final boolean runningProOrHigher; + private final boolean pdfUaAvailable; public EndpointConfiguration( ApplicationProperties applicationProperties, - @Qualifier("runningProOrHigher") boolean runningProOrHigher) { + @Qualifier("runningProOrHigher") boolean runningProOrHigher, + @Autowired(required = false) PdfaLevelAServiceInterface pdfaLevelAService) { this.applicationProperties = applicationProperties; this.runningProOrHigher = runningProOrHigher; + // The PDF/UA tagger ships in the proprietary module, and so do its endpoints. + this.pdfUaAvailable = pdfaLevelAService != null; init(); processEnvironmentConfigs(); } @@ -356,6 +362,7 @@ public class EndpointConfiguration { addEndpointToGroup("Convert", "pdf-to-img"); addEndpointToGroup("Convert", "img-to-pdf"); addEndpointToGroup("Convert", "pdf-to-pdfa"); + addEndpointToGroup("Convert", "pdf-to-ua"); addEndpointToGroup("Convert", "file-to-pdf"); addEndpointToGroup("Convert", "pdf-to-word"); addEndpointToGroup("Convert", "pdf-to-presentation"); @@ -395,6 +402,7 @@ public class EndpointConfiguration { // Backend-only endpoints (not in frontend tool registry endpoints) addEndpointToGroup("Security", "redact"); addEndpointToGroup("Security", "verify-pdf"); + addEndpointToGroup("Security", "accessibility-report"); addEndpointToGroup("Security", "sign"); // Adding endpoints to "Other" group @@ -529,6 +537,8 @@ public class EndpointConfiguration { addEndpointToGroup("Java", "json-to-pdf"); addEndpointToGroup("Java", "pdf-to-video"); addEndpointToGroup("Java", "verify-pdf"); + addEndpointToGroup("Java", "pdf-to-ua"); + addEndpointToGroup("Java", "accessibility-report"); addEndpointToGroup("Java", "flatten"); addEndpointToGroup("Java", "unlock-pdf-forms"); addEndpointToGroup("Java", "validate-signature"); @@ -600,6 +610,8 @@ public class EndpointConfiguration { // veraPDF dependent endpoints addEndpointToGroup("veraPDF", "verify-pdf"); + addEndpointToGroup("veraPDF", "pdf-to-ua"); + addEndpointToGroup("veraPDF", "accessibility-report"); // Pdftohtml dependent endpoints addEndpointToGroup("Pdftohtml", "pdf-to-html"); @@ -630,6 +642,11 @@ public class EndpointConfiguration { disableGroup("enterprise"); } + if (!pdfUaAvailable) { + disableEndpoint("pdf-to-ua"); + disableEndpoint("accessibility-report"); + } + if (!applicationProperties.getSystem().isEnableUrlToPDF()) { disableEndpoint("url-to-pdf"); } diff --git a/app/common/src/main/java/stirling/software/common/service/PdfaLevelAServiceInterface.java b/app/common/src/main/java/stirling/software/common/service/PdfaLevelAServiceInterface.java new file mode 100644 index 0000000000..2ee55719b2 --- /dev/null +++ b/app/common/src/main/java/stirling/software/common/service/PdfaLevelAServiceInterface.java @@ -0,0 +1,22 @@ +package stirling.software.common.service; + +import java.util.List; + +/** + * Raises a converted PDF/A file from conformance level B to level A, which needs the tagging the + * PDF/UA tagger does. Implemented only in the proprietary module; core builds convert at level B. + */ +public interface PdfaLevelAServiceInterface { + + /** + * @param levelA true only when the file was tagged and validated, so the claim is never a guess + */ + record Result(byte[] pdfBytes, boolean levelA, List warnings) {} + + /** + * @param part PDF/A part, 1 to 3; part 1 keeps its PDF 1.4 version + * @param alsoDeclareUa additionally claim PDF/UA, but only if it validates + */ + Result upgradeToLevelA( + byte[] pdfBytes, int part, String language, String title, boolean alsoDeclareUa); +} 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 f85880df5d..bfd3f53c67 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 @@ -4,7 +4,14 @@ import java.util.regex.Pattern; public class RequestUriUtils { - private static final Pattern SHARE_LINK_PATTERN = Pattern.compile("^/share/[^/]+/?$"); + // Share tokens are 36-char lowercase UUIDs (UUID.randomUUID().toString()); match exactly + private static final Pattern SHARE_LINK_PATTERN = + Pattern.compile( + "^/share/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/?$"); + // Invite tokens are 36-char lowercase UUIDs (UUID.randomUUID().toString()); match exactly + private static final Pattern INVITE_LINK_PATTERN = + Pattern.compile( + "^/invite/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/?$"); public static boolean isStaticResource(String requestURI) { return isStaticResource("", requestURI); @@ -69,7 +76,7 @@ public class RequestUriUtils { // cookie, so the server can't authenticate the navigation itself). The // portal gates access via its own auth gate + RequirePortalAccess, and its // data APIs stay protected, so serving the shell pre-auth is safe. - if (normalizedUri.equals("/processor") || normalizedUri.startsWith("/processor/")) { + if ("/processor".equals(normalizedUri) || normalizedUri.startsWith("/processor/")) { return true; } @@ -209,7 +216,9 @@ public class RequestUriUtils { // Workflow participant endpoints - access controlled by share tokens, not login || trimmedUri.startsWith("/api/v1/workflow/participant/") // Share-link SPA bootstrap; data APIs remain protected - || SHARE_LINK_PATTERN.matcher(trimmedUri).matches(); + || SHARE_LINK_PATTERN.matcher(trimmedUri).matches() + // Invite-accept SPA bootstrap; data APIs remain protected + || INVITE_LINK_PATTERN.matcher(trimmedUri).matches(); } private static String stripContextPath(String contextPath, String requestURI) { diff --git a/app/common/src/test/java/stirling/software/SPDF/config/EndpointConfigurationGapTest.java b/app/common/src/test/java/stirling/software/SPDF/config/EndpointConfigurationGapTest.java index 6275b49343..afc6fa7020 100644 --- a/app/common/src/test/java/stirling/software/SPDF/config/EndpointConfigurationGapTest.java +++ b/app/common/src/test/java/stirling/software/SPDF/config/EndpointConfigurationGapTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import stirling.software.SPDF.config.EndpointConfiguration.DisableReason; import stirling.software.SPDF.config.EndpointConfiguration.EndpointAvailability; import stirling.software.common.model.ApplicationProperties; +import stirling.software.common.service.PdfaLevelAServiceInterface; /** * Unit tests for {@link EndpointConfiguration}. The class wires up its endpoint/group registry in @@ -32,7 +33,14 @@ class EndpointConfigurationGapTest { * Construct an EndpointConfiguration with the given pro flag and current applicationProperties. */ private EndpointConfiguration build(boolean runningProOrHigher) { - return new EndpointConfiguration(applicationProperties, runningProOrHigher); + return build(runningProOrHigher, null); + } + + /** The PDF/UA service is only present in proprietary builds, so it is injected separately. */ + private EndpointConfiguration build( + boolean runningProOrHigher, PdfaLevelAServiceInterface pdfaLevelAService) { + return new EndpointConfiguration( + applicationProperties, runningProOrHigher, pdfaLevelAService); } /** Default config: not pro, no removals, url-to-pdf disabled (default System flag is false). */ @@ -177,6 +185,28 @@ class EndpointConfigurationGapTest { } } + @Nested + @DisplayName("PDF/UA availability") + class PdfUaTests { + + @Test + @DisplayName("the PDF/UA endpoints are off when the proprietary tagger is absent") + void disabledWithoutTagger() { + EndpointConfiguration config = build(false, null); + assertFalse(config.isEndpointEnabled("pdf-to-ua")); + assertFalse(config.isEndpointEnabled("accessibility-report")); + } + + @Test + @DisplayName("they are on once the tagger is on the classpath") + void enabledWithTagger() { + EndpointConfiguration config = + build(false, (pdfBytes, part, language, title, alsoDeclareUa) -> null); + assertTrue(config.isEndpointEnabled("pdf-to-ua")); + assertTrue(config.isEndpointEnabled("accessibility-report")); + } + } + @Nested @DisplayName("group enable / disable") class GroupTests { 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 0e399c1fae..72e5eae9a2 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 @@ -206,12 +206,24 @@ class RequestUriUtilsTest { @Test void testIsPublicAuthEndpoint_shareLinkTokenTrailingSlash() { - assertTrue(RequestUriUtils.isPublicAuthEndpoint("/share/abc123/", "")); + assertTrue( + RequestUriUtils.isPublicAuthEndpoint( + "/share/00dcac3a-fc7a-4989-9c4f-97745484d62f/", "")); } @Test void testIsPublicAuthEndpoint_shareLinkWithContextPath() { - assertTrue(RequestUriUtils.isPublicAuthEndpoint("/app/share/abc123", "/app")); + assertTrue( + RequestUriUtils.isPublicAuthEndpoint( + "/app/share/00dcac3a-fc7a-4989-9c4f-97745484d62f", "/app")); + } + + @Test + void testIsPublicAuthEndpoint_shareLinkWithInvalidTokenLength() { + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/share/abc123", "")); + assertFalse( + RequestUriUtils.isPublicAuthEndpoint( + "/share/00dcac3a-fc7a-4989-9c4f-97745484d62fa", "")); } @Test @@ -236,4 +248,86 @@ class RequestUriUtilsTest { RequestUriUtils.isPublicAuthEndpoint( "/api/v1/storage/share-links/abc123/metadata", "")); } + + // --- invite-accept SPA bootstrap --- + + private static final String INVITE_TOKEN = "06a20e7e-2e35-4e26-be7d-2dce14f28f12"; + + @Test + void testIsPublicAuthEndpoint_inviteLinkToken() { + assertTrue(RequestUriUtils.isPublicAuthEndpoint("/invite/" + INVITE_TOKEN, "")); + } + + @Test + void testIsPublicAuthEndpoint_inviteLinkTokenTrailingSlash() { + assertTrue(RequestUriUtils.isPublicAuthEndpoint("/invite/" + INVITE_TOKEN + "/", "")); + } + + @Test + void testIsPublicAuthEndpoint_inviteLinkWithContextPath() { + assertTrue(RequestUriUtils.isPublicAuthEndpoint("/app/invite/" + INVITE_TOKEN, "/app")); + } + + @Test + void testIsPublicAuthEndpoint_inviteRootNotPublic() { + // Avoid matching bare "/invite" or "/invite/" - must have a token segment + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/invite", "")); + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/invite/", "")); + } + + @Test + void testIsPublicAuthEndpoint_inviteNestedPathNotPublic() { + // Guard against future additions like /invite//foo becoming accidentally public + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/invite/" + INVITE_TOKEN + "/foo", "")); + } + + @Test + void testIsPublicAuthEndpoint_invitePrefixDoesNotOvermatch() { + // "/inviteX" must not match the invite pattern + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/inviteX", "")); + } + + @Test + void testIsPublicAuthEndpoint_inviteNonUuidTokenNotPublic() { + // Only exactly-shaped 36-char lowercase UUID tokens are treated as invite links + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/invite/abc123", "")); + } + + @Test + void testIsPublicAuthEndpoint_inviteUppercaseUuidNotPublic() { + // Tokens are generated lowercase by UUID.randomUUID().toString() + assertFalse( + RequestUriUtils.isPublicAuthEndpoint( + "/invite/06A20E7E-2E35-4E26-BE7D-2DCE14F28F12", "")); + } + + @Test + void testIsPublicAuthEndpoint_inviteWrongLengthNotPublic() { + // 35-char and 37-char UUID-like tokens are not valid UUIDs + assertFalse( + RequestUriUtils.isPublicAuthEndpoint( + "/invite/06a20e7e-2e35-4e26-be7d-2dce14f28f1", "")); + assertFalse( + RequestUriUtils.isPublicAuthEndpoint( + "/invite/06a20e7e-2e35-4e26-be7d-2dce14f28f122", "")); + } + + @Test + void testIsPublicAuthEndpoint_inviteWrongGroupingNotPublic() { + // Groups of 8-4-4-4-4 must not be shifted around (e.g. 4-4-4-4-8) + assertFalse( + RequestUriUtils.isPublicAuthEndpoint( + "/invite/06a2-0e7e-2e35-4e26-be7d2dce14f28f12", "")); + } + + @Test + void testIsPublicAuthEndpoint_inviteTokenInvalidCharsNotPublic() { + // Hex-only; anything outside [0-9a-f] or the UUID hyphens is rejected + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/invite/abc$123", "")); + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/invite/abc..123", "")); + assertFalse(RequestUriUtils.isPublicAuthEndpoint("/invite/abc%2F123", "")); + assertFalse( + RequestUriUtils.isPublicAuthEndpoint( + "/invite/06a20e7e-2e35-4e26-be7d-2dce14f28f1g", "")); + } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index 49bf4e4895..0354817315 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -11,6 +11,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.*; +import java.util.Locale; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -71,6 +72,7 @@ import org.apache.xmpbox.schema.PDFAIdentificationSchema; import org.apache.xmpbox.schema.XMPBasicSchema; import org.apache.xmpbox.xml.DomXmpParser; import org.apache.xmpbox.xml.XmpSerializer; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -83,7 +85,6 @@ import io.github.pixee.security.Filenames; import io.swagger.v3.oas.annotations.Operation; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.converters.PdfToPdfARequest; @@ -93,6 +94,7 @@ import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.enumeration.ResourceWeight; import stirling.software.common.model.tool.ToolFormat; import stirling.software.common.model.tool.ToolIO; +import stirling.software.common.service.PdfaLevelAServiceInterface; import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.ProcessExecutor; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; @@ -102,14 +104,26 @@ import stirling.software.common.util.WebResponseUtils; @ConvertApi @Slf4j -@RequiredArgsConstructor public class ConvertPDFToPDFA { private static final Pattern NON_PRINTABLE_ASCII = Pattern.compile("[^\\x20-\\x7E]"); private final RuntimePathConfig runtimePathConfig; private final stirling.software.SPDF.service.VeraPDFService veraPDFService; + // Level A needs the proprietary tagger; core builds convert at level B instead. + private final PdfaLevelAServiceInterface pdfaLevelAService; private final TempFileManager tempFileManager; + public ConvertPDFToPDFA( + RuntimePathConfig runtimePathConfig, + stirling.software.SPDF.service.VeraPDFService veraPDFService, + @Autowired(required = false) PdfaLevelAServiceInterface pdfaLevelAService, + TempFileManager tempFileManager) { + this.runtimePathConfig = runtimePathConfig; + this.veraPDFService = veraPDFService; + this.pdfaLevelAService = pdfaLevelAService; + this.tempFileManager = tempFileManager; + } + private static final String ICC_RESOURCE_PATH = "/icc/sRGB2014.icc"; private static final int PDFA_COMPATIBILITY_POLICY = 1; @@ -604,7 +618,10 @@ public class ConvertPDFToPDFA { return handlePdfXConversion(inputFile, outputFormat); } else { return handlePdfAConversion( - inputFile, outputFormat, request.getStrict() != null && request.getStrict()); + inputFile, + outputFormat, + request.getStrict() != null && request.getStrict(), + request.getPdfUa() != null && request.getPdfUa()); } } @@ -1815,8 +1832,64 @@ public class ConvertPDFToPDFA { return Files.readAllBytes(outputPdf); } + /** Tags a converted PDF/A for level A; must run after Ghostscript, which discards tags. */ + private PdfaLevelAServiceInterface.Result applyLevelA( + byte[] converted, + Path original, + PdfaProfile profile, + String baseFileName, + boolean declarePdfUa) { + if (!profile.requiresTagging()) { + return new PdfaLevelAServiceInterface.Result(converted, true, List.of()); + } + if (pdfaLevelAService == null) { + return new PdfaLevelAServiceInterface.Result( + converted, + false, + List.of( + "Level A tagging is not available in this build, so the file was left" + + " at conformance level B.")); + } + // Prefer the document's own title/language; hardcoding "en" mislabelled German reports. + // Read the original, not the converted bytes: Ghostscript discards /Lang, so probing its + // output always yields null and every document would be relabelled with the default. + String language = null; + String title = null; + try (PDDocument probe = Loader.loadPDF(original.toFile())) { + language = probe.getDocumentCatalog().getLanguage(); + title = probe.getDocumentInformation().getTitle(); + } catch (IOException e) { + log.debug("Could not read original title/language: {}", e.getMessage()); + } + if (language == null || language.isBlank()) { + try (PDDocument probe = Loader.loadPDF(converted)) { + language = probe.getDocumentCatalog().getLanguage(); + if (title == null || title.isBlank()) { + title = probe.getDocumentInformation().getTitle(); + } + } catch (IOException e) { + log.debug("Could not read converted title/language: {}", e.getMessage()); + } + } + PdfaLevelAServiceInterface.Result result = + pdfaLevelAService.upgradeToLevelA( + converted, + profile.getPart(), + language, + title != null && !title.isBlank() ? title : baseFileName, + declarePdfUa); + result.warnings().forEach(warning -> log.info("PDF/A level A: {}", warning)); + if (!result.levelA()) { + log.warn( + "{} requested but the document could not be tagged; returning level B", + profile.getDisplayName()); + } + return result; + } + private ResponseEntity handlePdfAConversion( - MultipartFile inputFile, String outputFormat, boolean strict) throws Exception { + MultipartFile inputFile, String outputFormat, boolean strict, boolean declarePdfUa) + throws Exception { PdfaProfile profile = PdfaProfile.fromRequest(outputFormat); // Get the original filename without extension @@ -1841,12 +1914,15 @@ public class ConvertPDFToPDFA { log.info("Using Ghostscript for PDF/A conversion to {}", profile.getDisplayName()); try { converted = convertWithGhostscript(inputPath, workingDir, profile); - String outputFilename = baseFileName + profile.outputSuffix(); + var levelA = + applyLevelA(converted, inputPath, profile, baseFileName, declarePdfUa); + converted = levelA.pdfBytes(); + String outputFilename = baseFileName + profile.outputSuffix(levelA.levelA()); validateAndWarnPdfA(converted, profile, "Ghostscript"); if (strict) { - verifyStrictCompliance(converted); + verifyStrictCompliance(converted, profile, levelA.levelA()); } TempFile tempOut = tempFileManager.createManagedTempFile(".pdf"); @@ -1867,13 +1943,15 @@ public class ConvertPDFToPDFA { } converted = convertWithPdfBoxMethod(inputPath, profile); - String outputFilename = baseFileName + profile.outputSuffix(); + var levelA = applyLevelA(converted, inputPath, profile, baseFileName, declarePdfUa); + converted = levelA.pdfBytes(); + String outputFilename = baseFileName + profile.outputSuffix(levelA.levelA()); // Validate with PDFBox preflight and warn if issues found validateAndWarnPdfA(converted, profile, "PDFBox/LibreOffice"); if (strict) { - verifyStrictCompliance(converted); + verifyStrictCompliance(converted, profile, levelA.levelA()); } TempFile tempOut = tempFileManager.createManagedTempFile(".pdf"); @@ -1889,11 +1967,56 @@ public class ConvertPDFToPDFA { } } - private void verifyStrictCompliance(byte[] pdfBytes) throws IOException { + /** True for a PDF/UA or WCAG result, which says nothing about archival conformance. */ + private static boolean isAccessibilityProfile( + stirling.software.SPDF.model.api.security.PDFVerificationResult result) { + String profile = result.getValidationProfile(); + if (profile == null) { + return false; + } + String normalised = profile.toLowerCase(Locale.ROOT); + return normalised.contains("ua") || normalised.contains("wcag"); + } + + /** + * True when a result speaks for the requested profile. Only archival results count, and a level + * B pass must never satisfy a level A request. + */ + private static boolean answersRequest( + PdfaProfile profile, + stirling.software.SPDF.model.api.security.PDFVerificationResult result) { + if (isAccessibilityProfile(result)) { + return false; + } + String standard = result.getStandard(); + if (standard == null || standard.length() < 2) { + return false; + } + if (standard.charAt(0) != Character.forDigit(profile.getPart(), 10)) { + return false; + } + return !profile.requiresTagging() || Character.toLowerCase(standard.charAt(1)) == 'a'; + } + + private void verifyStrictCompliance(byte[] pdfBytes, PdfaProfile profile, boolean levelAReached) + throws IOException { + // Tagging is the only route to level A, so an untagged file cannot answer a strict request. + if (!levelAReached) { + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Strict PDF/A mode enabled: the document could not be tagged, so " + + profile.getDisplayName() + + " was not reached. It is valid at level B."); + } try (InputStream is = new ByteArrayInputStream(pdfBytes)) { List results = veraPDFService.validatePDF(is); - boolean isCompliant = results.stream().anyMatch(result -> result.isCompliant()); + boolean isCompliant = + results.stream() + .filter(result -> answersRequest(profile, result)) + .anyMatch( + stirling.software.SPDF.model.api.security.PDFVerificationResult + ::isCompliant); if (!isCompliant) { String details = results.stream() @@ -1901,7 +2024,9 @@ public class ConvertPDFToPDFA { .collect(Collectors.joining("; ")); throw new ResponseStatusException( HttpStatus.BAD_REQUEST, - "Strict PDF/A mode enabled: Conversion is not perfectly compliant. Details: " + "Strict PDF/A mode enabled: the output is not perfectly compliant with " + + profile.getDisplayName() + + ". Details: " + details); } } catch (Exception e) { @@ -2466,11 +2591,16 @@ public class ConvertPDFToPDFA { @Getter private enum PdfaProfile { - PDF_A_1B(1, "PDF/A-1b", "_PDFA-1b.pdf", "1.4", Format.PDF_A1B, "pdfa-1"), - PDF_A_2B(2, "PDF/A-2b", "_PDFA-2b.pdf", "1.7", null, "pdfa", "pdfa-2", "pdfa-2b"), - PDF_A_3B(3, "PDF/A-3b", "_PDFA-3b.pdf", "1.7", null, "pdfa-3", "pdfa-3b"); + PDF_A_1B(1, "B", "PDF/A-1b", "_PDFA-1b.pdf", "1.4", Format.PDF_A1B, "pdfa-1"), + PDF_A_2B(2, "B", "PDF/A-2b", "_PDFA-2b.pdf", "1.7", null, "pdfa", "pdfa-2", "pdfa-2b"), + PDF_A_3B(3, "B", "PDF/A-3b", "_PDFA-3b.pdf", "1.7", null, "pdfa-3", "pdfa-3b"), + // Level A = level B plus tagging, declared language and Unicode text; tagged post-convert. + PDF_A_1A(1, "A", "PDF/A-1a", "_PDFA-1a.pdf", "1.4", Format.PDF_A1B, "pdfa-1a"), + PDF_A_2A(2, "A", "PDF/A-2a", "_PDFA-2a.pdf", "1.7", null, "pdfa-2a"), + PDF_A_3A(3, "A", "PDF/A-3a", "_PDFA-3a.pdf", "1.7", null, "pdfa-3a"); private final int part; + private final String conformanceLevel; private final String displayName; private final String suffix; private final String compatibilityLevel; @@ -2479,12 +2609,14 @@ public class ConvertPDFToPDFA { PdfaProfile( int part, + String conformanceLevel, String displayName, String suffix, String compatibilityLevel, Format preflightFormat, String... requestTokens) { this.part = part; + this.conformanceLevel = conformanceLevel; this.displayName = displayName; this.suffix = suffix; this.compatibilityLevel = compatibilityLevel; @@ -2495,6 +2627,10 @@ public class ConvertPDFToPDFA { .toList(); } + boolean requiresTagging() { + return "A".equals(conformanceLevel); + } + static PdfaProfile fromRequest(String requestToken) { if (requestToken == null) { return PDF_A_2B; @@ -2508,8 +2644,11 @@ public class ConvertPDFToPDFA { return match.orElse(PDF_A_2B); } - String outputSuffix() { - return suffix; + /** + * Names the file at the level actually reached; a level A name over level B content lies. + */ + String outputSuffix(boolean levelAReached) { + return levelAReached ? suffix : "_PDFA-" + part + "b.pdf"; } Optional preflightFormat() { diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPdfARequest.java b/app/core/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPdfARequest.java index bb0520a4ba..921663912b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPdfARequest.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPdfARequest.java @@ -14,9 +14,19 @@ public class PdfToPdfARequest extends PDFFile { @Schema( description = "The output format type (PDF/A or PDF/X)", requiredMode = Schema.RequiredMode.REQUIRED, - allowableValues = {"pdfa", "pdfa-1", "pdfa-2", "pdfa-2b", "pdfa-3", "pdfa-3b", "pdfx"}) + allowableValues = { + "pdfa", "pdfa-1", "pdfa-2", "pdfa-2b", "pdfa-3", "pdfa-3b", "pdfa-1a", "pdfa-2a", + "pdfa-3a", "pdfx" + }) private String outputFormat; + @Schema( + description = + "Also declare PDF/UA accessibility alongside PDF/A. Only applies to the level A" + + " formats, and the claim is written only if it validates.", + defaultValue = "false") + private Boolean pdfUa; + @Schema( description = "If true, the conversion will fail if the output is not perfectly compliant") diff --git a/app/core/src/main/java/stirling/software/SPDF/service/VeraPDFService.java b/app/core/src/main/java/stirling/software/SPDF/service/VeraPDFService.java index bb3c84534c..6361157b21 100644 --- a/app/core/src/main/java/stirling/software/SPDF/service/VeraPDFService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/VeraPDFService.java @@ -285,6 +285,8 @@ public class VeraPDFService { } } + // Never force PDF/UA here - it flags every ordinary document as non-compliant and doubles + // verify cost; /accessibility-report checks PDF/UA on demand. if (!hasPdfaDeclaration) { results.add(createNoPdfaDeclarationResult()); } diff --git a/app/core/src/main/resources/static/3rdPartyLicenses.json b/app/core/src/main/resources/static/3rdPartyLicenses.json index fa852846b3..c411735489 100644 --- a/app/core/src/main/resources/static/3rdPartyLicenses.json +++ b/app/core/src/main/resources/static/3rdPartyLicenses.json @@ -14,6 +14,13 @@ "moduleLicense": "GNU Lesser General Public License", "moduleLicenseUrl": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" }, + { + "moduleName": "ch.qos.logback:logback-classic", + "moduleUrl": "http://www.qos.ch", + "moduleVersion": "1.6.1", + "moduleLicense": "LGPL-2.1-only", + "moduleLicenseUrl": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + }, { "moduleName": "ch.qos.logback:logback-core", "moduleUrl": "http://www.qos.ch", @@ -21,6 +28,13 @@ "moduleLicense": "GNU Lesser General Public License", "moduleLicenseUrl": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" }, + { + "moduleName": "ch.qos.logback:logback-core", + "moduleUrl": "http://www.qos.ch", + "moduleVersion": "1.6.1", + "moduleLicense": "LGPL-2.1-only", + "moduleLicenseUrl": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + }, { "moduleName": "com.adobe.xmp:xmpcore", "moduleUrl": "https://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html", @@ -182,7 +196,7 @@ { "moduleName": "com.github.mwiede:jsch", "moduleUrl": "https://github.com/mwiede/jsch", - "moduleVersion": "0.2.23", + "moduleVersion": "2.28.6", "moduleLicense": "Revised BSD", "moduleLicenseUrl": "https://github.com/mwiede/jsch/blob/master/LICENSE.txt" }, @@ -513,27 +527,45 @@ "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, + { + "moduleName": "com.twelvemonkeys.common:common-image", + "moduleVersion": "3.14.0", + "moduleLicense": "The BSD License", + "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" + }, { "moduleName": "com.twelvemonkeys.common:common-io", "moduleVersion": "3.13.1", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, + { + "moduleName": "com.twelvemonkeys.common:common-io", + "moduleVersion": "3.14.0", + "moduleLicense": "The BSD License", + "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" + }, { "moduleName": "com.twelvemonkeys.common:common-lang", "moduleVersion": "3.13.1", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, + { + "moduleName": "com.twelvemonkeys.common:common-lang", + "moduleVersion": "3.14.0", + "moduleLicense": "The BSD License", + "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" + }, { "moduleName": "com.twelvemonkeys.imageio:imageio-batik", - "moduleVersion": "3.13.1", + "moduleVersion": "3.14.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.imageio:imageio-bmp", - "moduleVersion": "3.13.1", + "moduleVersion": "3.14.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, @@ -543,9 +575,15 @@ "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, + { + "moduleName": "com.twelvemonkeys.imageio:imageio-core", + "moduleVersion": "3.14.0", + "moduleLicense": "The BSD License", + "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" + }, { "moduleName": "com.twelvemonkeys.imageio:imageio-jpeg", - "moduleVersion": "3.13.1", + "moduleVersion": "3.14.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, @@ -555,9 +593,15 @@ "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, + { + "moduleName": "com.twelvemonkeys.imageio:imageio-metadata", + "moduleVersion": "3.14.0", + "moduleLicense": "The BSD License", + "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" + }, { "moduleName": "com.twelvemonkeys.imageio:imageio-psd", - "moduleVersion": "3.13.1", + "moduleVersion": "3.14.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, @@ -567,12 +611,24 @@ "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, + { + "moduleName": "com.twelvemonkeys.imageio:imageio-tiff", + "moduleVersion": "3.14.0", + "moduleLicense": "The BSD License", + "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" + }, { "moduleName": "com.twelvemonkeys.imageio:imageio-webp", "moduleVersion": "3.13.1", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, + { + "moduleName": "com.twelvemonkeys.imageio:imageio-webp", + "moduleVersion": "3.14.0", + "moduleLicense": "The BSD License", + "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" + }, { "moduleName": "com.vladsch.flexmark:flexmark", "moduleVersion": "0.64.8", @@ -758,7 +814,7 @@ { "moduleName": "commons-net:commons-net", "moduleUrl": "https://commons.apache.org/proper/commons-net/", - "moduleVersion": "3.11.1", + "moduleVersion": "3.13.0", "moduleLicense": "Apache-2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -1213,24 +1269,48 @@ "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "net.shibboleth:shib-networking", + "moduleVersion": "9.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "net.shibboleth:shib-security", "moduleVersion": "9.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "net.shibboleth:shib-security", + "moduleVersion": "9.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "net.shibboleth:shib-support", "moduleVersion": "9.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "net.shibboleth:shib-support", + "moduleVersion": "9.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "net.shibboleth:shib-velocity", "moduleVersion": "9.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "net.shibboleth:shib-velocity", + "moduleVersion": "9.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.antlr:antlr4-runtime", "moduleUrl": "https://www.antlr.org/", @@ -1325,13 +1405,6 @@ "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, - { - "moduleName": "org.apache.httpcomponents:httpclient", - "moduleUrl": "http://hc.apache.org/httpcomponents-client", - "moduleVersion": "4.5.13", - "moduleLicense": "Apache License, Version 2.0", - "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" - }, { "moduleName": "org.apache.httpcomponents:httpclient", "moduleUrl": "http://hc.apache.org/httpcomponents-client-ga", @@ -1456,6 +1529,13 @@ "moduleLicense": "Apache-2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.apache.santuario:xmlsec", + "moduleUrl": "https://www.apache.org/", + "moduleVersion": "3.0.6", + "moduleLicense": "Apache-2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.apache.tomcat.embed:tomcat-embed-el", "moduleUrl": "https://tomcat.apache.org/", @@ -1470,6 +1550,13 @@ "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.apache.velocity:velocity-engine-core", + "moduleUrl": "https://www.apache.org/", + "moduleVersion": "2.4.1", + "moduleLicense": "Apache-2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.apache.xmlbeans:xmlbeans", "moduleUrl": "https://xmlbeans.apache.org/", @@ -1647,6 +1734,13 @@ "moduleLicense": "GNU Lesser General Public License", "moduleLicenseUrl": "http://www.gnu.org/licenses/lgpl-3.0.txt" }, + { + "moduleName": "org.cryptacular:cryptacular", + "moduleUrl": "https://www.cryptacular.org", + "moduleVersion": "1.3.0", + "moduleLicense": "GNU Lesser General Public License", + "moduleLicenseUrl": "https://www.gnu.org/licenses/lgpl-3.0.txt" + }, { "moduleName": "org.eclipse.angus:angus-activation", "moduleUrl": "https://www.eclipse.org", @@ -2016,78 +2110,156 @@ "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-core-api", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-core-impl", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-core-impl", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-messaging-api", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-messaging-api", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-profile-api", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-profile-api", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-saml-api", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-saml-api", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-saml-impl", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-saml-impl", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-security-api", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-security-api", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-security-impl", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-security-impl", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-soap-api", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-soap-api", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-soap-impl", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-soap-impl", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-storage-api", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-storage-api", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-xmlsec-api", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-xmlsec-api", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.opensaml:opensaml-xmlsec-impl", "moduleVersion": "5.1.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, + { + "moduleName": "org.opensaml:opensaml-xmlsec-impl", + "moduleVersion": "5.2.2", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.ow2.asm:asm", "moduleUrl": "http://asm.ow2.org", @@ -2132,7 +2304,7 @@ }, { "moduleName": "org.simplejavamail:core-module", - "moduleVersion": "9.2.0", + "moduleVersion": "9.3.1", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -2145,13 +2317,13 @@ }, { "moduleName": "org.simplejavamail:outlook-module", - "moduleVersion": "9.2.0", + "moduleVersion": "9.3.1", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "org.simplejavamail:simple-java-mail", - "moduleVersion": "9.2.0", + "moduleVersion": "9.3.1", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -2564,6 +2736,13 @@ "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, + { + "moduleName": "org.springframework.security:spring-security-core", + "moduleUrl": "https://spring.io/projects/spring-security", + "moduleVersion": "7.1.0", + "moduleLicense": "Apache License, Version 2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" + }, { "moduleName": "org.springframework.security:spring-security-crypto", "moduleUrl": "https://spring.io/projects/spring-security", @@ -2606,6 +2785,13 @@ "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, + { + "moduleName": "org.springframework.security:spring-security-saml2-service-provider", + "moduleUrl": "https://spring.io/projects/spring-security", + "moduleVersion": "7.1.0", + "moduleLicense": "Apache License, Version 2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" + }, { "moduleName": "org.springframework.security:spring-security-web", "moduleUrl": "https://spring.io/projects/spring-security", @@ -2805,207 +2991,207 @@ }, { "moduleName": "software.amazon.awssdk:annotations", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { - "moduleName": "software.amazon.awssdk:apache-client", - "moduleVersion": "2.44.12", + "moduleName": "software.amazon.awssdk:apache5-client", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:arns", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:auth", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:aws-core", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:aws-query-protocol", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:aws-xml-protocol", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:checksums", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:checksums-spi", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:crt-core", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:endpoints-spi", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:http-auth", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:http-auth-aws", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:http-auth-aws-eventstream", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:http-auth-spi", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:http-client-spi", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:identity-spi", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:json-utils", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:metrics-spi", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:netty-nio-client", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:profiles", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:protocol-core", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:regions", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:retries", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:retries-spi", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:s3", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:sdk-core", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:third-party-jackson-core", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:url-connection-client", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:utils", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, { "moduleName": "software.amazon.awssdk:utils-lite", "moduleUrl": "https://aws.amazon.com/sdkforjava", - "moduleVersion": "2.44.12", + "moduleVersion": "2.51.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://aws.amazon.com/apache2.0" }, diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFAGapTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFAGapTest.java index b64776665b..ea693ddd27 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFAGapTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFAGapTest.java @@ -46,6 +46,7 @@ import stirling.software.SPDF.model.api.converters.PdfToPdfARequest; import stirling.software.SPDF.model.api.security.PDFVerificationResult; import stirling.software.SPDF.service.VeraPDFService; import stirling.software.common.configuration.RuntimePathConfig; +import stirling.software.common.service.PdfaLevelAServiceInterface; import stirling.software.common.util.TempFileManager; /** @@ -62,10 +63,12 @@ class ConvertPDFToPDFAGapTest { @Mock private RuntimePathConfig runtimePathConfig; @Mock private VeraPDFService veraPDFService; + @Mock private PdfaLevelAServiceInterface pdfaLevelAService; @Mock private TempFileManager tempFileManager; private ConvertPDFToPDFA newController() { - return new ConvertPDFToPDFA(runtimePathConfig, veraPDFService, tempFileManager); + return new ConvertPDFToPDFA( + runtimePathConfig, veraPDFService, pdfaLevelAService, tempFileManager); } // ---- reflection helpers ---------------------------------------------------------------- @@ -161,9 +164,21 @@ class ConvertPDFToPDFAGapTest { } private String suffixOf(Object profile) throws Exception { - Method m = profile.getClass().getDeclaredMethod("outputSuffix"); + return suffixOf(profile, true); + } + + private String suffixOf(Object profile, boolean levelAReached) throws Exception { + Method m = profile.getClass().getDeclaredMethod("outputSuffix", boolean.class); m.setAccessible(true); - return (String) m.invoke(profile); + return (String) m.invoke(profile, levelAReached); + } + + @Test + @DisplayName("a level A profile falls back to the level B name when tagging failed") + void levelANotReachedIsNamedLevelB() throws Exception { + assertThat(suffixOf(resolveProfile("pdfa-1a"), false)).isEqualTo("_PDFA-1b.pdf"); + assertThat(suffixOf(resolveProfile("pdfa-2a"), false)).isEqualTo("_PDFA-2b.pdf"); + assertThat(suffixOf(resolveProfile("pdfa-3a"), true)).isEqualTo("_PDFA-3a.pdf"); } @Test @@ -717,6 +732,30 @@ class ConvertPDFToPDFAGapTest { @DisplayName("verifyStrictCompliance (VeraPDFService mocked)") class StrictCompliance { + private Object profile(String token) throws Exception { + Class enumClass = null; + for (Class inner : ConvertPDFToPDFA.class.getDeclaredClasses()) { + if (inner.getSimpleName().equals("PdfaProfile")) { + enumClass = inner; + } + } + Method m = enumClass.getDeclaredMethod("fromRequest", String.class); + m.setAccessible(true); + return m.invoke(null, token); + } + + private Throwable verify(String token, boolean levelAReached) throws Exception { + ConvertPDFToPDFA controller = newController(); + return catchThrowable( + () -> + invokeInstance( + controller, + "verifyStrictCompliance", + (Object) "dummy".getBytes(), + profile(token), + levelAReached)); + } + @Test @DisplayName("compliant result passes without throwing") void compliantPasses() throws Exception { @@ -726,14 +765,7 @@ class ConvertPDFToPDFAGapTest { ok.setComplianceSummary("PDF/A-1b compliant"); when(veraPDFService.validatePDF(any())).thenReturn(List.of(ok)); - ConvertPDFToPDFA controller = newController(); - assertThatCode( - () -> - invokeInstance( - controller, - "verifyStrictCompliance", - (Object) "dummy".getBytes())) - .doesNotThrowAnyException(); + assertThat(verify("pdfa-1", true)).isNull(); } @Test @@ -745,34 +777,70 @@ class ConvertPDFToPDFAGapTest { bad.setComplianceSummary("PDF/A-1b with errors"); when(veraPDFService.validatePDF(any())).thenReturn(List.of(bad)); - ConvertPDFToPDFA controller = newController(); - ResponseStatusException ex = - (ResponseStatusException) - catchThrowable( - () -> - invokeInstance( - controller, - "verifyStrictCompliance", - (Object) "dummy".getBytes())); + ResponseStatusException ex = (ResponseStatusException) verify("pdfa-1", true); assertThat(ex).isNotNull(); assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); assertThat(ex.getReason()).contains("PDF/A-1b with errors"); } + @Test + @DisplayName("a level B pass does not satisfy a level A request") + void levelBDoesNotSatisfyLevelA() throws Exception { + PDFVerificationResult ok = new PDFVerificationResult(); + ok.setCompliant(true); + ok.setStandard("1b"); + ok.setComplianceSummary("PDF/A-1b compliant"); + when(veraPDFService.validatePDF(any())).thenReturn(List.of(ok)); + + ResponseStatusException ex = (ResponseStatusException) verify("pdfa-1a", true); + assertThat(ex).isNotNull(); + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(ex.getReason()).contains("PDF/A-1a"); + } + + @Test + @DisplayName("a level A result satisfies a level A request") + void levelASatisfiesLevelA() throws Exception { + PDFVerificationResult ok = new PDFVerificationResult(); + ok.setCompliant(true); + ok.setStandard("2a"); + ok.setComplianceSummary("PDF/A-2a compliant"); + when(veraPDFService.validatePDF(any())).thenReturn(List.of(ok)); + + assertThat(verify("pdfa-2a", true)).isNull(); + } + + @Test + @DisplayName("untagged output fails a level A request before validation runs") + void untaggedLevelARequestFails() throws Exception { + ResponseStatusException ex = (ResponseStatusException) verify("pdfa-2a", false); + assertThat(ex).isNotNull(); + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(ex.getReason()).contains("could not be tagged"); + verifyNoInteractions(veraPDFService); + } + + @Test + @DisplayName("a compliant PDF/UA result never satisfies a strict PDF/A request") + void accessibilityResultIsIgnored() throws Exception { + PDFVerificationResult ua = new PDFVerificationResult(); + ua.setCompliant(true); + ua.setStandard("ua1"); + ua.setValidationProfile("ua1"); + ua.setComplianceSummary("PDF/UA-1 compliant"); + when(veraPDFService.validatePDF(any())).thenReturn(List.of(ua)); + + ResponseStatusException ex = (ResponseStatusException) verify("pdfa-2b", true); + assertThat(ex).isNotNull(); + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + @Test @DisplayName("empty result list is treated as non-compliant -> 400") void emptyResultsTreatedNonCompliant() throws Exception { when(veraPDFService.validatePDF(any())).thenReturn(Collections.emptyList()); - ConvertPDFToPDFA controller = newController(); - ResponseStatusException ex = - (ResponseStatusException) - catchThrowable( - () -> - invokeInstance( - controller, - "verifyStrictCompliance", - (Object) "dummy".getBytes())); + ResponseStatusException ex = (ResponseStatusException) verify("pdfa-1", true); assertThat(ex).isNotNull(); assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } @@ -782,15 +850,7 @@ class ConvertPDFToPDFAGapTest { void serviceErrorWrappedAs500() throws Exception { when(veraPDFService.validatePDF(any())).thenThrow(new IOException("boom")); - ConvertPDFToPDFA controller = newController(); - ResponseStatusException ex = - (ResponseStatusException) - catchThrowable( - () -> - invokeInstance( - controller, - "verifyStrictCompliance", - (Object) "dummy".getBytes())); + ResponseStatusException ex = (ResponseStatusException) verify("pdfa-1", true); assertThat(ex).isNotNull(); assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFAMoreTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFAMoreTest.java index e9d9b9ce1d..d56a283464 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFAMoreTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFAMoreTest.java @@ -42,6 +42,7 @@ import org.springframework.mock.web.MockMultipartFile; import stirling.software.SPDF.model.api.converters.PdfToPdfARequest; import stirling.software.SPDF.service.VeraPDFService; import stirling.software.common.configuration.RuntimePathConfig; +import stirling.software.common.service.PdfaLevelAServiceInterface; import stirling.software.common.util.ProcessExecutor; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; import stirling.software.common.util.TempFile; @@ -63,10 +64,12 @@ class ConvertPDFToPDFAMoreTest { @Mock private RuntimePathConfig runtimePathConfig; @Mock private VeraPDFService veraPDFService; + @Mock private PdfaLevelAServiceInterface pdfaLevelAService; @Mock private TempFileManager tempFileManager; private ConvertPDFToPDFA newController() { - return new ConvertPDFToPDFA(runtimePathConfig, veraPDFService, tempFileManager); + return new ConvertPDFToPDFA( + runtimePathConfig, veraPDFService, pdfaLevelAService, tempFileManager); } private static ResponseEntity streamingOk(byte[] bytes) { diff --git a/app/core/src/test/java/stirling/software/SPDF/service/VeraPDFServicePdfaFixtureTest.java b/app/core/src/test/java/stirling/software/SPDF/service/VeraPDFServicePdfaFixtureTest.java index 38043780d9..e071ece3fb 100644 --- a/app/core/src/test/java/stirling/software/SPDF/service/VeraPDFServicePdfaFixtureTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/service/VeraPDFServicePdfaFixtureTest.java @@ -90,7 +90,9 @@ class VeraPDFServicePdfaFixtureTest { () -> service.validatePDF(new ByteArrayInputStream(pdfBytes)), "Empty veraPDF flavour list must not surface as IndexOutOfBoundsException"); - assertEquals(1, results.size()); + // One result: PDF/UA is checked by the dedicated accessibility-report endpoint, not here. + assertEquals(1, results.size(), () -> "Expected a single PDF/A result, got: " + results); + PDFVerificationResult result = results.get(0); assertEquals("not-pdfa", result.getStandard()); assertFalse(result.isDeclaredPdfa()); @@ -161,13 +163,22 @@ class VeraPDFServicePdfaFixtureTest { } } + /** The PDF/A result; every document is also checked against PDF/UA, so filter that one out. */ private PDFVerificationResult onlyResult(byte[] pdfBytes) throws Exception { List results = service.validatePDF(new ByteArrayInputStream(pdfBytes)); assertNotNull(results); - assertEquals(1, results.size(), () -> "Expected a single result, got: " + results); - return results.get(0); + List pdfaResults = + results.stream().filter(r -> !isUaResult(r)).toList(); + assertEquals( + 1, pdfaResults.size(), () -> "Expected a single PDF/A result, got: " + results); + return pdfaResults.get(0); + } + + private static boolean isUaResult(PDFVerificationResult result) { + String profile = result.getValidationProfile(); + return profile != null && profile.toLowerCase().contains("ua"); } private static String messages(PDFVerificationResult result) { diff --git a/app/proprietary/build.gradle b/app/proprietary/build.gradle index c20241dbb5..e66288238a 100644 --- a/app/proprietary/build.gradle +++ b/app/proprietary/build.gradle @@ -27,7 +27,7 @@ dependencies { api 'org.springframework.boot:spring-boot-starter-cache' api 'com.github.ben-manes.caffeine:caffeine' implementation 'org.springframework.boot:spring-boot-starter-data-redis' - api 'io.swagger.core.v3:swagger-core-jakarta:2.2.46' + api 'io.swagger.core.v3:swagger-core-jakarta:2.2.53' implementation "com.bucket4j:bucket4j_jdk17-core:${bucket4jVersion}" // Lettuce-backed Bucket4j ProxyManager used by ValkeyRateLimitStore for cluster-wide // token-bucket rate limiting (parity with in-process Bucket4j semantics; no fixed-window @@ -37,6 +37,15 @@ dependencies { // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" + // PDF/UA tagging and its validation oracle. + implementation 'org.verapdf:validation-model:1.30.2' + // CVE-2025-66453: Explicit rhino 1.7.15 to override verapdf's 1.7.13 + implementation "org.mozilla:rhino:${rhinoVersion}" + // veraPDF still uses javax.xml.bind, not the new jakarta namespace + implementation 'javax.xml.bind:jaxb-api:2.3.1' + runtimeOnly 'com.sun.xml.bind:jaxb-impl:2.3.9' + runtimeOnly 'com.sun.xml.bind:jaxb-core:4.0.9' + implementation "com.google.code.gson:gson:${gsonVersion}" // jinjava/jjwt transitively request older Jackson 2 versions; declare the current diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/converters/ConvertPdfToPdfUa.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/converters/ConvertPdfToPdfUa.java new file mode 100644 index 0000000000..7f5241388f --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/converters/ConvertPdfToPdfUa.java @@ -0,0 +1,176 @@ +package stirling.software.proprietary.controller.api.converters; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.multipart.MultipartFile; + +import io.github.pixee.security.Filenames; +import io.swagger.v3.oas.annotations.Operation; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import stirling.software.common.annotations.AutoJobPostMapping; +import stirling.software.common.annotations.api.ConvertApi; +import stirling.software.common.enumeration.ResourceWeight; +import stirling.software.common.model.tool.ToolFormat; +import stirling.software.common.model.tool.ToolIO; +import stirling.software.common.util.ExceptionUtils; +import stirling.software.common.util.TempFile; +import stirling.software.common.util.TempFileManager; +import stirling.software.common.util.WebResponseUtils; +import stirling.software.proprietary.model.api.converters.PdfToPdfUaRequest; +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.TaggingOptions; +import stirling.software.proprietary.service.ua.PdfUaConversionService; + +/** Converts a PDF to PDF/UA; response headers say whether the result actually conforms. */ +@ConvertApi +@Slf4j +@RequiredArgsConstructor +public class ConvertPdfToPdfUa { + + private static final String HEADER_DECLARED = "X-Stirling-UA-Declared"; + private static final String HEADER_FAILURES = "X-Stirling-UA-Failures"; + private static final String HEADER_ALT_NEEDED = "X-Stirling-UA-Figures-Needing-Alt"; + private static final String HEADER_WARNINGS = "X-Stirling-UA-Warnings"; + + /** Any line ending, so descriptions pasted from any platform parse the same. */ + private static final Pattern NEWLINE = Pattern.compile("\\R"); + + private final PdfUaConversionService conversionService; + private final TempFileManager tempFileManager; + + @AutoJobPostMapping( + consumes = MediaType.MULTIPART_FORM_DATA_VALUE, + value = "/pdf/ua", + resourceWeight = ResourceWeight.LARGE_WEIGHT) + @ToolIO(produces = ToolFormat.PDF) + @Operation( + summary = "Convert a PDF to PDF/UA-1 or PDF/UA-2", + description = + "Tags the document, marks decorative content as artifacts, embeds fonts and" + + " applies the document-level requirements of PDF/UA, then validates" + + " the result. A conformance declaration is written only if validation" + + " passes, so the returned file never claims more than it delivers.") + public ResponseEntity pdfToPdfUa(@ModelAttribute PdfToPdfUaRequest request) + throws IOException { + + MultipartFile input = request.getFileInput(); + if (input == null || input.isEmpty()) { + throw ExceptionUtils.createPdfFileRequiredException(); + } + + String originalName = Filenames.toSimpleFileName(input.getOriginalFilename()); + String stem = stripExtension(originalName == null ? "document" : originalName); + PdfUaProfile profile = PdfUaProfile.fromRequest(request.getProfile()); + + TaggingOptions options = + TaggingOptions.builder() + .profile(profile) + .title(request.getTitle()) + .fallbackTitle(stem) + // Only used when the document declares no language of its own. + .language( + request.getLanguage() == null || request.getLanguage().isBlank() + ? "en-GB" + : request.getLanguage()) + .overrideLanguage( + request.getOverrideLanguage() != null + && request.getOverrideLanguage()) + .existingTags(existingTags(request.getExistingTags())) + .figurePolicy(figurePolicy(request.getFigurePolicy())) + .embedFonts(request.getEmbedFonts() == null || request.getEmbedFonts()) + .altTextByFigure(parseAltText(request.getAltText())) + .build(); + + PdfUaConversionOutcome outcome = conversionService.convert(input.getBytes(), options); + + log.info( + "Converted '{}' to {}: declared={}, {} remaining failure(s)", + originalName, + profile.displayName(), + outcome.declared(), + outcome.validation().totalFailures()); + + outcome.warnings().forEach(warning -> log.info("PDF/UA warning: {}", warning)); + + // Streamed from a temp file so a large conversion does not hold a second heap copy. + String suffix = outcome.declared() ? "_pdfua" + profile.part() : "_tagged"; + TempFile tempOut = tempFileManager.createManagedTempFile(".pdf"); + try { + Files.write(tempOut.getPath(), outcome.pdfBytes()); + } catch (IOException e) { + tempOut.close(); + throw e; + } + ResponseEntity response = + WebResponseUtils.pdfFileToWebResponse(tempOut, stem + suffix + ".pdf"); + + return ResponseEntity.status(response.getStatusCode()) + .headers(response.getHeaders()) + .header(HEADER_DECLARED, String.valueOf(outcome.declared())) + .header(HEADER_FAILURES, String.valueOf(outcome.validation().totalFailures())) + .header( + HEADER_ALT_NEEDED, + String.valueOf(outcome.tagging().figuresNeedingAltText())) + // Count only: warning text is multi-line prose, which HTTP headers mangle. + .header(HEADER_WARNINGS, String.valueOf(outcome.warnings().size())) + .body(response.getBody()); + } + + /** + * Parses newline-separated {@code key=description} pairs, keyed as the report hands them out. + * Only the first "=" splits, since a description may contain one. + */ + public static Map parseAltText(String raw) { + if (raw == null || raw.isBlank()) { + return Map.of(); + } + Map parsed = new LinkedHashMap<>(); + for (String line : NEWLINE.split(raw)) { + int split = line.indexOf('='); + if (split <= 0) { + continue; + } + String key = line.substring(0, split).strip(); + String description = line.substring(split + 1).strip(); + if (!key.isEmpty() && !description.isEmpty()) { + parsed.put(key, description); + } + } + return parsed; + } + + private static TaggingOptions.ExistingTags existingTags(String value) { + if (value == null) { + return TaggingOptions.ExistingTags.AUTO; + } + return switch (value.trim().toLowerCase()) { + case "keep" -> TaggingOptions.ExistingTags.KEEP; + case "rebuild" -> TaggingOptions.ExistingTags.REBUILD; + default -> TaggingOptions.ExistingTags.AUTO; + }; + } + + private static TaggingOptions.FigurePolicy figurePolicy(String value) { + if (value != null && value.trim().equalsIgnoreCase("mark-decorative")) { + return TaggingOptions.FigurePolicy.MARK_DECORATIVE; + } + return TaggingOptions.FigurePolicy.REQUIRE_ALT; + } + + private static String stripExtension(String filename) { + int dot = filename.lastIndexOf('.'); + return dot > 0 ? filename.substring(0, dot) : filename; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/security/AccessibilityReportController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/security/AccessibilityReportController.java new file mode 100644 index 0000000000..043734dad8 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/security/AccessibilityReportController.java @@ -0,0 +1,67 @@ +package stirling.software.proprietary.controller.api.security; + +import java.io.IOException; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import stirling.software.common.annotations.AutoJobPostMapping; +import stirling.software.common.annotations.api.SecurityApi; +import stirling.software.common.enumeration.ResourceWeight; +import stirling.software.common.model.tool.ToolFormat; +import stirling.software.common.model.tool.ToolIO; +import stirling.software.common.util.ExceptionUtils; +import stirling.software.proprietary.model.api.ua.AccessibilityReport; +import stirling.software.proprietary.model.api.ua.AccessibilityReportRequest; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.service.ua.AccessibilityAuditService; + +/** Reports how accessible a document is, without modifying it. */ +@SecurityApi +@RequiredArgsConstructor +@Slf4j +public class AccessibilityReportController { + + private final AccessibilityAuditService auditService; + + @ToolIO(produces = ToolFormat.JSON) + @Operation( + summary = "Report a document's accessibility standing", + description = + "Validates the document against PDF/UA and reports what fails, which failures" + + " can be fixed automatically, and which checks still need a person." + + " Does not modify the file.") + // Costs a full veraPDF pass plus the converter's own layout analysis over every page. + @AutoJobPostMapping( + value = "/accessibility-report", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE, + resourceWeight = ResourceWeight.LARGE_WEIGHT) + public ResponseEntity report( + @ModelAttribute AccessibilityReportRequest request) { + + MultipartFile file = request.getFileInput(); + if (file == null || file.isEmpty()) { + throw ExceptionUtils.createPdfFileRequiredException(); + } + PdfUaProfile profile = PdfUaProfile.fromRequest(request.getProfile()); + try { + AccessibilityReport report = auditService.audit(file.getBytes(), profile); + log.info( + "Accessibility report for '{}': tagged={}, {} issue(s)", + file.getOriginalFilename(), + report.isTagged(), + report.getIssues().size()); + return ResponseEntity.ok(report); + } catch (IOException e) { + throw ExceptionUtils.createRuntimeException( + "error.ioException", "Could not read the PDF: {0}", e, e.getMessage()); + } + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/model/api/converters/PdfToPdfUaRequest.java b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/converters/PdfToPdfUaRequest.java new file mode 100644 index 0000000000..8981398001 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/converters/PdfToPdfUaRequest.java @@ -0,0 +1,73 @@ +package stirling.software.proprietary.model.api.converters; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.common.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper = true) +public class PdfToPdfUaRequest extends PDFFile { + + @Schema( + description = "PDF/UA conformance level to target", + defaultValue = "ua1", + allowableValues = {"ua1", "ua2"}) + private String profile; + + @Schema( + description = + "Document title, required by PDF/UA. Falls back to the first heading, then the" + + " filename.") + private String title; + + @Schema( + description = + "Document language as a BCP-47 tag, for example en-GB. Applied only when the" + + " document does not already declare one, unless overrideLanguage is" + + " set.", + defaultValue = "en-GB") + private String language; + + @Schema( + description = + "Replace the language the document already declares. Off by default, so a" + + " document is never relabelled into a language it is not written in.", + defaultValue = "false") + private Boolean overrideLanguage; + + @Schema( + description = + "What to do with an existing structure tree: keep it, rebuild it, or decide" + + " automatically", + defaultValue = "auto", + allowableValues = {"auto", "keep", "rebuild"}) + private String existingTags; + + @Schema( + description = + "How to treat images with no description. require-alt leaves them undescribed so" + + " the report asks for input; mark-decorative treats every image as" + + " decoration.", + defaultValue = "require-alt", + allowableValues = {"require-alt", "mark-decorative"}) + private String figurePolicy; + + @Schema( + description = + "Embed fonts the document references but does not carry. Required for" + + " conformance and needs Ghostscript.", + defaultValue = "true") + private Boolean embedFonts; + + @Schema( + description = + "Alternative descriptions for figures, as key=text pairs separated by newlines." + + " Keys come from the accessibility-report endpoint's" + + " figuresNeedingDescription list, for example \"0:12=Bar chart of" + + " quarterly revenue\". Descriptions are never invented, so without" + + " these an illustrated document cannot claim conformance.") + private String altText; +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityIssue.java b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityIssue.java new file mode 100644 index 0000000000..0c2a86ad99 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityIssue.java @@ -0,0 +1,38 @@ +package stirling.software.proprietary.model.api.ua; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; + +/** One accessibility problem, grouped across all of its occurrences. */ +@Data +@Schema(description = "A single accessibility issue found in a document") +public class AccessibilityIssue { + + @Schema(description = "ISO 14289 clause, e.g. 7.3") + private String clause; + + @Schema(description = "Test number within the clause") + private String testNumber; + + @Schema(description = "Plain-English description of the problem") + private String message; + + @Schema(description = "The validator's own wording, for support and debugging") + private String technicalMessage; + + @Schema(description = "error or warning") + private String severity = "error"; + + @Schema(description = "Standard the check came from, e.g. PDF/UA-1") + private String specification; + + @Schema(description = "Where the problem was found, when the validator reports it") + private String location; + + @Schema(description = "How many times this issue occurs") + private int occurrences; + + @Schema(description = "True when the converter can fix this without human input") + private boolean autoFixable; +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityReport.java b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityReport.java new file mode 100644 index 0000000000..bc810635d1 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityReport.java @@ -0,0 +1,63 @@ +package stirling.software.proprietary.model.api.ua; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; + +/** + * A document's accessibility standing. The machine/human split is load-bearing: veraPDF covers only + * about half of the Matterhorn Protocol, so a clean automated pass is not "accessible". + */ +@Data +@Schema(description = "Accessibility standing of a document") +public class AccessibilityReport { + + @Schema(description = "Profile the document was checked against, e.g. PDF/UA-1") + private String profile; + + @Schema(description = "Whether the document has a structure tree at all") + private boolean tagged; + + @Schema(description = "Whether the document declares PDF/UA conformance in its metadata") + private boolean declaresConformance; + + @Schema(description = "Whether every automated check passed") + private boolean passesAutomatedChecks; + + @Schema(description = "Automated checks that failed, grouped by rule") + private List issues = List.of(); + + @Schema(description = "Things a person still has to verify; automation cannot decide these") + private List humanChecks = List.of(); + + @Schema(description = "How many of the failing checks the converter can fix on its own") + private int automaticallyFixable; + + @Schema(description = "How many need information from the user, such as alternative text") + private int needsInput; + + @Schema( + description = + "Figures that need an alternative description. Each carries the key to pass" + + " back in the conversion request's altTextByFigure map, so a caller" + + " can enumerate what is missing and then supply it.") + private List figuresNeedingDescription = List.of(); + + @Schema(description = "Document-level facts that drive most failures") + private Summary summary = new Summary(); + + @Data + @Schema(description = "Quick document-level facts") + public static class Summary { + private int pages; + private boolean hasTitle; + private boolean displaysDocTitle; + private boolean hasLanguage; + private boolean allFontsEmbedded; + private int unembeddedFonts; + private int figures; + private boolean encrypted; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityReportRequest.java b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityReportRequest.java new file mode 100644 index 0000000000..178d2637b2 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/AccessibilityReportRequest.java @@ -0,0 +1,19 @@ +package stirling.software.proprietary.model.api.ua; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import stirling.software.common.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AccessibilityReportRequest extends PDFFile { + + @Schema( + description = "Profile to check against", + defaultValue = "ua1", + allowableValues = {"ua1", "ua2"}) + private String profile; +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/FigureDescriptor.java b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/FigureDescriptor.java new file mode 100644 index 0000000000..1c960d87e7 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/FigureDescriptor.java @@ -0,0 +1,18 @@ +package stirling.software.proprietary.model.api.ua; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * One figure needing an alternative description, which is never invented. key is the + * altTextByFigure key "pageIndex:ordinal"; page is 1-based; kind is "figure" or "formula". + */ +@Schema(description = "A figure that needs an alternative description") +public record FigureDescriptor( + String key, + int page, + String kind, + float x, + float y, + float width, + float height, + String existingAlt) {} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/PdfUaConversionOutcome.java b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/PdfUaConversionOutcome.java new file mode 100644 index 0000000000..dcef97c916 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/PdfUaConversionOutcome.java @@ -0,0 +1,26 @@ +package stirling.software.proprietary.model.api.ua; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Result of a PDF/UA conversion. + * + * @param declared whether a {@code pdfuaid} conformance claim was written into {@code pdfBytes} + */ +@Schema(description = "Result of converting a document to PDF/UA") +public record PdfUaConversionOutcome( + byte[] pdfBytes, + boolean declared, + UaValidationResult validation, + TaggingSummary tagging, + List warnings) { + + @Schema(description = "What the tagging pass produced") + public record TaggingSummary( + boolean rebuiltStructure, + int taggedElements, + int artifacts, + int figuresNeedingAltText) {} +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/UaValidationResult.java b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/UaValidationResult.java new file mode 100644 index 0000000000..5e494c35be --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/model/api/ua/UaValidationResult.java @@ -0,0 +1,18 @@ +package stirling.software.proprietary.model.api.ua; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Outcome of validating against one PDF/UA profile. compliant means every automated check passed, + * which is not the same as usable by assistive technology; totalFailures is ungrouped. + */ +@Schema(description = "Result of validating a document against a PDF/UA profile") +public record UaValidationResult( + String profile, boolean compliant, List issues, int totalFailures) { + + public boolean hasIssues() { + return !issues.isEmpty(); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/ArtifactType.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/ArtifactType.java new file mode 100644 index 0000000000..78df1fbc27 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/ArtifactType.java @@ -0,0 +1,23 @@ +package stirling.software.proprietary.pdf.ua; + +/** Artifact subtypes (ISO 32000-1 14.8.2.2). Artifacts are excluded from the structure tree. */ +public enum ArtifactType { + /** Running heads, folios, page numbers. Required by PDF/UA-1 clause 7.8. */ + PAGINATION("Pagination"), + /** Rules, boxes, and other layout ornamentation. */ + LAYOUT("Layout"), + /** Cut marks and colour bars. */ + PAGE("Page"), + /** Background graphics with no informational content. */ + BACKGROUND("Background"); + + private final String subtype; + + ArtifactType(String subtype) { + this.subtype = subtype; + } + + public String subtype() { + return subtype; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/BBox.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/BBox.java new file mode 100644 index 0000000000..f2fbf97438 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/BBox.java @@ -0,0 +1,48 @@ +package stirling.software.proprietary.pdf.ua; + +/** An axis-aligned rectangle in PDF user space, with y increasing upwards. */ +public record BBox(float x0, float y0, float x1, float y1) { + + public static final BBox EMPTY = new BBox(0, 0, 0, 0); + + public static BBox of(float x, float y, float width, float height) { + return new BBox(x, y, x + width, y + height); + } + + public float width() { + return x1 - x0; + } + + public float height() { + return y1 - y0; + } + + public float centreX() { + return (x0 + x1) / 2f; + } + + public BBox union(BBox other) { + if (other == null || other.isEmpty()) { + return this; + } + if (isEmpty()) { + return other; + } + return new BBox( + Math.min(x0, other.x0), + Math.min(y0, other.y0), + Math.max(x1, other.x1), + Math.max(y1, other.y1)); + } + + public boolean isEmpty() { + return x1 <= x0 || y1 <= y0; + } + + /** Horizontal overlap with another box as a fraction of the narrower box's width. */ + public float horizontalOverlap(BBox other) { + float overlap = Math.min(x1, other.x1) - Math.max(x0, other.x0); + float narrower = Math.min(width(), other.width()); + return narrower <= 0 ? 0 : Math.max(0, overlap) / narrower; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/DocumentStructure.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/DocumentStructure.java new file mode 100644 index 0000000000..5922895453 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/DocumentStructure.java @@ -0,0 +1,84 @@ +package stirling.software.proprietary.pdf.ua; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import lombok.Getter; +import lombok.Setter; + +/** The derived logical structure of a document, ready for serialisation into a structure tree. */ +@Getter +@Setter +public class DocumentStructure { + + /** Top-level blocks in document reading order. */ + private final List blocks = new ArrayList<>(); + + /** Warnings raised during analysis, surfaced in the conversion report. */ + private final List warnings = new ArrayList<>(); + + private String title; + private String language; + + /** True when real text was wrapped as artifacts, which blocks any conformance claim. */ + private boolean textSuppressed; + + /** Body text size used as the baseline for heading detection, in points. */ + private float bodyFontSize; + + public void add(StructBlock block) { + blocks.add(block); + } + + public void warn(String message) { + if (!warnings.contains(message)) { + warnings.add(message); + } + } + + public void visit(Consumer visitor) { + blocks.forEach(block -> block.visit(visitor)); + } + + public int count(StructType type) { + int[] total = {0}; + visit( + block -> { + if (block.getType() == type) { + total[0]++; + } + }); + return total[0]; + } + + public int artifactCount() { + int[] total = {0}; + visit( + block -> { + if (block.isArtifact()) { + total[0]++; + } + }); + return total[0]; + } + + /** Figures with no alternative description, the most common PDF/UA failure. */ + public List figuresWithoutAlt() { + List missing = new ArrayList<>(); + visit( + block -> { + if ((block.getType() == StructType.FIGURE + || block.getType() == StructType.FORMULA) + && (block.getAlt() == null || block.getAlt().isBlank()) + && (block.getActualText() == null || block.getActualText().isBlank())) { + missing.add(block); + } + }); + return missing; + } + + public boolean isEmpty() { + return blocks.isEmpty(); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/LayoutAnalyzer.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/LayoutAnalyzer.java new file mode 100644 index 0000000000..11c510d6ea --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/LayoutAnalyzer.java @@ -0,0 +1,831 @@ +package stirling.software.proprietary.pdf.ua; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +/** + * Derives a logical structure from extracted lines and graphics, reusing {@code HeadingDetector}'s + * heuristics. Degrades to paragraphs rather than guessing, since a wrong tag misleads readers. + */ +@Slf4j +public class LayoutAnalyzer { + + private static final Pattern BULLET = Pattern.compile("^[•‣◦⁃∙·▪●■o\\-\\*\\+]\\s+.*"); + private static final Pattern ORDERED = + Pattern.compile("^(\\d{1,3}|[a-zA-Z]|[ivxlcIVXLC]{1,5})[\\.\\)]\\s+.*"); + private static final Pattern PAGE_NUMBER = + Pattern.compile( + "^(page\\s+)?\\d{1,4}(\\s*(of|/)\\s*\\d{1,4})?$", Pattern.CASE_INSENSITIVE); + private static final Pattern DIGITS = Pattern.compile("\\d+"); + + /** Fraction of page height treated as the running head / foot band. */ + private static final float MARGIN_BAND = 0.10f; + + /** A line must exceed the body size by this ratio before it can be a heading. */ + private static final float HEADING_RATIO = 1.10f; + + /** Sizes within this many points are treated as the same heading tier. */ + private static final float TIER_TOLERANCE = 0.4f; + + private static final int MAX_HEADING_WORDS = 12; + + /** Word gap beyond this multiple of the font size separates table cells. */ + private static final float CELL_GAP_RATIO = 1.2f; + + /** Images smaller than this in either dimension are decoration, not content. */ + private static final float MIN_FIGURE_SIZE = 12f; + + /** A size used by more than this share of lines is body text, however large the median says. */ + private static final float MAX_HEADING_LINE_SHARE = 0.2f; + + /** Consecutive lines sharing a size are a text block; headings appear alone. */ + private static final int MAX_HEADING_RUN = 3; + + /** A vector thinner than this in either dimension is a rule or border, not a drawing. */ + private static final float MIN_VECTOR_THICKNESS = 3f; + + /** Vector clusters smaller than this are ornament; larger ones are probably a chart. */ + private static final float MIN_VECTOR_FIGURE_SIZE = 40f; + + /** A drawing is built from several strokes; one big rectangle is a panel, not a chart. */ + private static final int MIN_VECTOR_FIGURE_OPS = 4; + + /** More text than this inside the region means shading behind content, not a drawing. */ + private static final int MAX_LINES_INSIDE_FIGURE = 2; + + public DocumentStructure analyse(List pages) { + DocumentStructure structure = new DocumentStructure(); + float bodySize = bodyFontSize(pages); + structure.setBodyFontSize(bodySize); + Map tiers = headingTiers(pages, bodySize); + Map> artifactLines = repeatedMarginLines(pages, bodySize); + + for (PageContent page : pages) { + analysePage( + page, + structure, + bodySize, + tiers, + artifactLines.getOrDefault(page.pageIndex(), List.of())); + } + + List suppressedPages = + pages.stream() + .filter(PageContent::linesDropped) + .map(PageContent::pageIndex) + .toList(); + if (!suppressedPages.isEmpty()) { + structure.setTextSuppressed(true); + structure.warn( + "Text on page(s) " + + suppressedPages.stream() + .map(i -> String.valueOf(i + 1)) + .collect(Collectors.joining(", ")) + + " could not be tagged reliably and was marked as artifacts. The" + + " converter will not claim conformance while real text is hidden" + + " from assistive technology."); + } + + normaliseHeadingLevels(structure); + structure.setTitle(deriveTitle(structure)); + return structure; + } + + // --- Document-wide statistics ----------------------------------------- + + /** Character-weighted median line size, which is far more stable than a plain median. */ + static float bodyFontSize(List pages) { + Map weights = new HashMap<>(); + for (PageContent page : pages) { + for (TextLineInfo line : page.lines()) { + if (line.dominantFontSize() > 0 && !line.isBlank()) { + weights.merge(line.dominantFontSize(), line.charCount(), Integer::sum); + } + } + } + if (weights.isEmpty()) { + return 0f; + } + int total = weights.values().stream().mapToInt(Integer::intValue).sum(); + List> sorted = + weights.entrySet().stream().sorted(Map.Entry.comparingByKey()).toList(); + int seen = 0; + for (Map.Entry entry : sorted) { + seen += entry.getValue(); + if (seen >= total / 2) { + return entry.getKey(); + } + } + return sorted.get(sorted.size() - 1).getKey(); + } + + /** Maps each distinct heading size to a 1-based level, largest size first. */ + static Map headingTiers(List pages, float bodySize) { + if (bodySize <= 0) { + return Map.of(); + } + // A size used by a large share of the lines is body text, whatever the median says. + Map lineCounts = new HashMap<>(); + int totalLines = 0; + for (PageContent page : pages) { + for (TextLineInfo line : page.lines()) { + if (!line.isBlank()) { + lineCounts.merge(line.dominantFontSize(), 1, Integer::sum); + totalLines++; + } + } + } + int headingLineCeiling = Math.max(1, (int) (totalLines * MAX_HEADING_LINE_SHARE)); + + // Headings do not cluster; a run of same-size lines is a text block, not headings. + Map longestRun = new HashMap<>(); + for (PageContent page : pages) { + Float runSize = null; + int runLength = 0; + for (TextLineInfo line : page.lines()) { + if (line.isBlank()) { + continue; + } + float size = line.dominantFontSize(); + if (runSize != null && Float.compare(size, runSize) == 0) { + runLength++; + } else { + runSize = size; + runLength = 1; + } + int seen = longestRun.getOrDefault(size, 0); + if (runLength > seen) { + longestRun.put(size, runLength); + } + } + } + + List sizes = new ArrayList<>(); + for (PageContent page : pages) { + for (TextLineInfo line : page.lines()) { + if (isHeadingCandidate(line) + && line.dominantFontSize() > bodySize * HEADING_RATIO + && lineCounts.getOrDefault(line.dominantFontSize(), 0) <= headingLineCeiling + && longestRun.getOrDefault(line.dominantFontSize(), 0) < MAX_HEADING_RUN) { + sizes.add(line.dominantFontSize()); + } + } + } + List distinct = sizes.stream().distinct().sorted(Comparator.reverseOrder()).toList(); + + Map tiers = new LinkedHashMap<>(); + int level = 0; + Float previous = null; + for (Float size : distinct) { + if (previous == null || previous - size > TIER_TOLERANCE) { + level = Math.min(level + 1, 6); + previous = size; + } + tiers.put(size, level); + } + return tiers; + } + + /** + * Claims a line's operators word run by word run; claiming the whole ordinal interval would + * swallow anything drawn between them, an image included. + */ + private static void claimLine(StructBlock block, TextLineInfo line) { + // Sort by ordinal, not position: merging out-of-order runs silently drops them to + // /Artifact, hiding them from assistive technology while the file still validates. + List words = + line.words().stream() + .filter(w -> !w.isBlank()) + .sorted(Comparator.comparingInt(WordInfo::startOrdinal)) + .toList(); + if (words.isEmpty()) { + block.addRange(line.startOrdinal(), line.endOrdinal()); + return; + } + int start = words.get(0).startOrdinal(); + int end = words.get(0).endOrdinal(); + for (int i = 1; i < words.size(); i++) { + WordInfo word = words.get(i); + if (word.startOrdinal() <= end + 1) { + end = Math.max(end, word.endOrdinal()); + } else { + block.addRange(start, end); + start = word.startOrdinal(); + end = word.endOrdinal(); + } + } + block.addRange(start, end); + } + + static boolean isHeadingCandidate(TextLineInfo line) { + String text = line.text().strip(); + if (text.isEmpty() || line.wordCount() > MAX_HEADING_WORDS) { + return false; + } + char last = text.charAt(text.length() - 1); + return last != '.' && last != '!' && last != '?'; + } + + /** + * Finds lines in the head/foot bands whose text repeats across pages. Digits are masked first + * so that "Page 4" and "Page 5" count as the same running foot. + */ + static Map> repeatedMarginLines(List pages) { + return repeatedMarginLines(pages, bodyFontSize(pages)); + } + + static Map> repeatedMarginLines( + List pages, float bodySize) { + Map> result = new HashMap<>(); + if (pages.isEmpty()) { + return result; + } + Map counts = new HashMap<>(); + Map> candidates = new HashMap<>(); + + for (PageContent page : pages) { + float height = page.mediaBox().height(); + if (height <= 0) { + continue; + } + float topEdge = page.mediaBox().y1() - height * MARGIN_BAND; + float bottomEdge = page.mediaBox().y0() + height * MARGIN_BAND; + List inBand = new ArrayList<>(); + for (TextLineInfo line : page.lines()) { + if (line.bbox().y0() >= topEdge || line.bbox().y1() <= bottomEdge) { + inBand.add(line); + counts.merge(mask(line.text()), 1, Integer::sum); + } + } + candidates.put(page.pageIndex(), inBand); + } + + int threshold = Math.max(2, pages.size() / 2); + for (Map.Entry> entry : candidates.entrySet()) { + List artifacts = new ArrayList<>(); + for (TextLineInfo line : entry.getValue()) { + boolean repeats = + pages.size() >= 3 && counts.getOrDefault(mask(line.text()), 0) >= threshold; + boolean pageNumber = PAGE_NUMBER.matcher(line.text().strip()).matches(); + // Masked digits merge "Section 1" and "Section 2"; size is the tie-break that stops + // a real heading being demoted, as running heads are never larger than body text. + boolean looksLikeChrome = + bodySize <= 0 || line.dominantFontSize() <= bodySize * 1.05f; + if (pageNumber || (repeats && looksLikeChrome)) { + artifacts.add(line); + } + } + result.put(entry.getKey(), artifacts); + } + return result; + } + + private static String mask(String text) { + return DIGITS.matcher(text.strip().toLowerCase()).replaceAll("#").replaceAll("\\s+", " "); + } + + // --- Per-page analysis ------------------------------------------------- + + private void analysePage( + PageContent page, + DocumentStructure structure, + float bodySize, + Map tiers, + List marginArtifacts) { + + for (TextLineInfo line : marginArtifacts) { + StructBlock artifact = StructBlock.artifact(ArtifactType.PAGINATION, page.pageIndex()); + claimLine(artifact, line); + artifact.setBbox(line.bbox()); + artifact.setText(line.text()); + structure.add(artifact); + } + + // Identity set, not List.contains: TextLineInfo is a record whose equals walks its word + // list, so a linear scan per line is quadratic with a deep comparison inside it. + java.util.Set marginSet = Collections.newSetFromMap(new IdentityHashMap<>()); + marginSet.addAll(marginArtifacts); + List body = + page.lines().stream() + .filter(line -> !line.isBlank() && !marginSet.contains(line)) + .sorted(readingOrder(page)) + .toList(); + + List blocks = new ArrayList<>(); + int index = 0; + while (index < body.size()) { + TextLineInfo line = body.get(index); + + int tableEnd = tableRunEnd(body, index); + if (tableEnd > index) { + StructBlock table = buildTable(body.subList(index, tableEnd + 1), page.pageIndex()); + if (table != null) { + blocks.add(table); + index = tableEnd + 1; + continue; + } + } + + int listEnd = listRunEnd(body, index); + if (listEnd > index) { + blocks.add(buildList(body.subList(index, listEnd + 1), page.pageIndex())); + index = listEnd + 1; + continue; + } + + Integer level = headingLevel(line, tiers); + if (level != null) { + StructBlock heading = new StructBlock(StructType.heading(level), page.pageIndex()); + claimLine(heading, line); + heading.setBbox(line.bbox()); + heading.setText(line.text()); + blocks.add(heading); + index++; + continue; + } + + int paragraphEnd = paragraphRunEnd(body, index, tiers, bodySize); + blocks.add(buildParagraph(body.subList(index, paragraphEnd + 1), page.pageIndex())); + index = paragraphEnd + 1; + } + + // Form XObject text is attributed to its Do, so a Figure too would double-claim it. + Set claimed = new HashSet<>(); + for (StructBlock block : blocks) { + block.visit( + node -> + node.getRanges() + .forEach( + range -> { + for (int i = range.start(); i <= range.end(); i++) { + claimed.add(i); + } + })); + } + blocks.addAll(buildGraphics(page, structure, claimed)); + blocks.forEach(structure::add); + } + + /** + * Orders lines top-to-bottom, splitting into columns first when the page is clearly + * multi-column. Without this, a two-column page reads as interleaved half-sentences. + */ + private Comparator readingOrder(PageContent page) { + Float gutter = detectGutter(page); + if (gutter == null) { + return Comparator.comparingDouble((TextLineInfo l) -> -l.bbox().y1()) + .thenComparingDouble(l -> l.bbox().x0()); + } + return Comparator.comparingInt((TextLineInfo l) -> l.bbox().centreX() < gutter ? 0 : 1) + .thenComparingDouble(l -> -l.bbox().y1()) + .thenComparingDouble(l -> l.bbox().x0()); + } + + /** + * Returns the x of a vertical gutter when the page is two-column, else null. A gutter must sit + * near the middle, be crossed by almost no line, and have substantial text on both sides. + */ + static Float detectGutter(PageContent page) { + List lines = page.lines().stream().filter(line -> !line.isBlank()).toList(); + if (lines.size() < 8) { + return null; + } + float pageWidth = page.mediaBox().width(); + if (pageWidth <= 0) { + return null; + } + float centre = page.mediaBox().x0() + pageWidth / 2f; + long crossing = + lines.stream() + .filter( + line -> + line.bbox().x0() < centre - 5 + && line.bbox().x1() > centre + 5) + .count(); + if (crossing > lines.size() * 0.1) { + return null; + } + long left = lines.stream().filter(line -> line.bbox().centreX() < centre).count(); + long right = lines.size() - left; + boolean balanced = left > lines.size() * 0.25 && right > lines.size() * 0.25; + return balanced ? centre : null; + } + + private static Integer headingLevel(TextLineInfo line, Map tiers) { + if (!isHeadingCandidate(line)) { + return null; + } + return tiers.get(line.dominantFontSize()); + } + + // --- Paragraphs -------------------------------------------------------- + + private static int paragraphRunEnd( + List lines, int start, Map tiers, float bodySize) { + int end = start; + for (int i = start + 1; i < lines.size(); i++) { + TextLineInfo previous = lines.get(i - 1); + TextLineInfo current = lines.get(i); + if (headingLevel(current, tiers) != null || startsListItem(current)) { + break; + } + float gap = previous.bbox().y0() - current.bbox().y1(); + float leading = Math.max(bodySize, current.bbox().height()); + boolean sameBlock = gap < leading * 0.8f && gap > -leading; + boolean sentenceEnded = endsSentence(previous.text()); + if (!sameBlock || (sentenceEnded && gap > leading * 0.4f)) { + break; + } + end = i; + } + return end; + } + + private static boolean endsSentence(String text) { + String stripped = text.strip(); + if (stripped.isEmpty()) { + return false; + } + char last = stripped.charAt(stripped.length() - 1); + return last == '.' || last == '!' || last == '?'; + } + + private static StructBlock buildParagraph(List lines, int pageIndex) { + StructBlock paragraph = new StructBlock(StructType.P, pageIndex); + BBox box = BBox.EMPTY; + StringBuilder text = new StringBuilder(); + for (TextLineInfo line : lines) { + claimLine(paragraph, line); + box = box.union(line.bbox()); + if (text.length() > 0) { + text.append(' '); + } + text.append(line.text().strip()); + } + paragraph.setBbox(box); + paragraph.setText(text.toString()); + return paragraph; + } + + // --- Lists ------------------------------------------------------------- + + static boolean startsListItem(TextLineInfo line) { + String text = line.text().strip(); + return BULLET.matcher(text).matches() || ORDERED.matcher(text).matches(); + } + + private static int listRunEnd(List lines, int start) { + if (!startsListItem(lines.get(start))) { + return start; + } + float indent = lines.get(start).bbox().x0(); + int end = start; + for (int i = start + 1; i < lines.size(); i++) { + TextLineInfo line = lines.get(i); + boolean isItem = startsListItem(line) && Math.abs(line.bbox().x0() - indent) < 6f; + boolean isContinuation = !startsListItem(line) && line.bbox().x0() > indent + 2f; + if (!isItem && !isContinuation) { + break; + } + end = i; + } + // A single marker is a stray character, not a list. + long items = + lines.subList(start, end + 1).stream() + .filter(LayoutAnalyzer::startsListItem) + .count(); + return items >= 2 ? end : start; + } + + private static StructBlock buildList(List lines, int pageIndex) { + StructBlock list = new StructBlock(StructType.L, pageIndex); + list.setListNumbering(listNumbering(lines.get(0))); + BBox box = BBox.EMPTY; + StructBlock currentBody = null; + + for (TextLineInfo line : lines) { + box = box.union(line.bbox()); + if (startsListItem(line) || currentBody == null) { + StructBlock item = new StructBlock(StructType.LI, pageIndex); + StructBlock body = new StructBlock(StructType.LBODY, pageIndex); + claimLine(body, line); + body.setBbox(line.bbox()); + body.setText(line.text()); + item.addChild(body); + item.setBbox(line.bbox()); + list.addChild(item); + currentBody = body; + } else { + claimLine(currentBody, line); + currentBody.setBbox(currentBody.getBbox().union(line.bbox())); + currentBody.setText(currentBody.getText() + " " + line.text().strip()); + } + } + list.setBbox(box); + return list; + } + + private static String listNumbering(TextLineInfo first) { + String text = first.text().strip(); + if (BULLET.matcher(text).matches()) { + return "Disc"; + } + char c = text.charAt(0); + if (Character.isDigit(c)) { + return "Decimal"; + } + if ("ivxlc".indexOf(Character.toLowerCase(c)) >= 0 && text.length() > 1) { + return Character.isUpperCase(c) ? "UpperRoman" : "LowerRoman"; + } + return Character.isUpperCase(c) ? "UpperAlpha" : "LowerAlpha"; + } + + // --- Tables ------------------------------------------------------------ + + /** Splits a line into cells wherever the gap between words exceeds the cell threshold. */ + static List> splitCells(TextLineInfo line) { + List words = line.words().stream().filter(w -> !w.isBlank()).toList(); + List> cells = new ArrayList<>(); + if (words.isEmpty()) { + return cells; + } + float threshold = Math.max(line.dominantFontSize(), 1f) * CELL_GAP_RATIO; + List current = new ArrayList<>(); + current.add(words.get(0)); + for (int i = 1; i < words.size(); i++) { + float gap = words.get(i).bbox().x0() - words.get(i - 1).bbox().x1(); + if (gap > threshold) { + cells.add(List.copyOf(current)); + current = new ArrayList<>(); + } + current.add(words.get(i)); + } + cells.add(List.copyOf(current)); + return cells; + } + + /** + * Index of the last line of a table run starting at {@code start}, or {@code start} if none. + */ + private static int tableRunEnd(List lines, int start) { + int end = start; + for (int i = start; i < lines.size(); i++) { + if (splitCells(lines.get(i)).size() < 2) { + break; + } + end = i; + } + return end > start ? end : start; + } + + /** + * Builds a Table when the run really looks tabular and each cell owns its own operators. + * Returns null when it does not, so the caller falls back to paragraphs. + */ + private static StructBlock buildTable(List rows, int pageIndex) { + if (rows.size() < 2) { + return null; + } + List>> grid = new ArrayList<>(); + for (TextLineInfo row : rows) { + if (!row.wordsAreSeparable()) { + log.debug("Table row shares operators between cells; falling back to paragraphs"); + return null; + } + grid.add(splitCells(row)); + } + int columns = grid.get(0).size(); + long consistent = grid.stream().filter(row -> row.size() == columns).count(); + if (columns < 2 || consistent < Math.max(2, grid.size() * 0.6)) { + return null; + } + + boolean headerRow = looksLikeHeader(rows, grid); + StructBlock table = new StructBlock(StructType.TABLE, pageIndex); + BBox box = BBox.EMPTY; + + for (int r = 0; r < grid.size(); r++) { + List> cells = grid.get(r); + if (cells.size() != columns) { + continue; + } + StructBlock tr = new StructBlock(StructType.TR, pageIndex); + boolean isHeader = headerRow && r == 0; + for (List cell : cells) { + StructBlock td = + new StructBlock(isHeader ? StructType.TH : StructType.TD, pageIndex); + if (isHeader) { + td.setScope("Column"); + } + BBox cellBox = BBox.EMPTY; + StringBuilder text = new StringBuilder(); + int from = cell.get(0).startOrdinal(); + int to = cell.get(cell.size() - 1).endOrdinal(); + for (WordInfo word : cell) { + cellBox = cellBox.union(word.bbox()); + if (text.length() > 0) { + text.append(' '); + } + text.append(word.text()); + } + td.addRange(from, to); + td.setBbox(cellBox); + td.setText(text.toString()); + tr.addChild(td); + box = box.union(cellBox); + } + tr.setBbox(box); + table.addChild(tr); + } + table.setBbox(box); + if (table.getChildren().size() < 2) { + return null; + } + // Clause 7.5 needs equal cell counts per row; a ragged table fails validation outright. + long distinctWidths = + table.getChildren().stream() + .map(row -> row.getChildren().size()) + .distinct() + .count(); + if (distinctWidths != 1) { + log.debug("Discarding a table whose rows have different cell counts"); + return null; + } + return table; + } + + /** The first row is a header when it is bold, or when only later rows carry numbers. */ + private static boolean looksLikeHeader( + List rows, List>> grid) { + if (rows.get(0).bold()) { + return true; + } + boolean firstHasDigits = DIGITS.matcher(rows.get(0).text()).find(); + boolean laterHasDigits = + rows.subList(1, rows.size()).stream() + .anyMatch(row -> DIGITS.matcher(row.text()).find()); + return !firstHasDigits && laterHasDigits; + } + + // --- Graphics ---------------------------------------------------------- + + private List buildGraphics( + PageContent page, DocumentStructure structure, java.util.Set claimed) { + List blocks = new ArrayList<>(); + boolean warnedForms = false; + + // Vectors cluster: a chart is many strokes in one region, a rule is a single thin one. + java.util.Set vectorFigureOrdinals = vectorFigureOrdinals(page, claimed); + + for (MarkableOp op : page.ops()) { + if (op.kind() == MarkableOp.Kind.TEXT || claimed.contains(op.ordinal())) { + continue; + } + BBox box = op.bbox(); + + if (op.kind() == MarkableOp.Kind.VECTOR) { + StructBlock block; + if (vectorFigureOrdinals.contains(op.ordinal())) { + block = new StructBlock(StructType.FIGURE, page.pageIndex()); + } else { + block = StructBlock.artifact(ArtifactType.LAYOUT, page.pageIndex()); + } + block.addRange(op.ordinal(), op.ordinal()); + block.setBbox(box); + blocks.add(block); + continue; + } + + boolean decorative = box.width() < MIN_FIGURE_SIZE || box.height() < MIN_FIGURE_SIZE; + if (decorative) { + StructBlock artifact = StructBlock.artifact(ArtifactType.LAYOUT, page.pageIndex()); + artifact.addRange(op.ordinal(), op.ordinal()); + artifact.setBbox(box); + blocks.add(artifact); + continue; + } + + if (op.kind() == MarkableOp.Kind.FORM && !warnedForms) { + structure.warn( + "Content inside form XObjects was tagged as a single region because its" + + " text is not separately addressable; review those areas."); + warnedForms = true; + } + + StructBlock figure = new StructBlock(StructType.FIGURE, page.pageIndex()); + figure.addRange(op.ordinal(), op.ordinal()); + figure.setBbox(box); + blocks.add(figure); + } + return blocks; + } + + /** + * Finds vector operators belonging to a substantial drawing rather than page furniture; thin + * paths are rules and table borders, and a short run is ornament. + */ + private static Set vectorFigureOrdinals( + PageContent page, java.util.Set claimed) { + // A chart's plot area is mostly empty, while shading sits behind the text it decorates. + Set result = new HashSet<>(); + List run = new ArrayList<>(); + BBox extent = BBox.EMPTY; + + for (MarkableOp op : page.ops()) { + boolean substantial = + op.kind() == MarkableOp.Kind.VECTOR + && !claimed.contains(op.ordinal()) + && !op.bbox().isEmpty() + && op.bbox().width() >= MIN_VECTOR_THICKNESS + && op.bbox().height() >= MIN_VECTOR_THICKNESS; + if (substantial) { + run.add(op); + extent = extent.isEmpty() ? op.bbox() : extent.union(op.bbox()); + continue; + } + flushVectorRun(run, extent, page.lines(), result); + run = new ArrayList<>(); + extent = BBox.EMPTY; + } + flushVectorRun(run, extent, page.lines(), result); + return result; + } + + private static void flushVectorRun( + List run, + BBox extent, + List lines, + java.util.Set result) { + if (run.size() < MIN_VECTOR_FIGURE_OPS + || extent.width() < MIN_VECTOR_FIGURE_SIZE + || extent.height() < MIN_VECTOR_FIGURE_SIZE) { + return; + } + if (overlappingLines(extent, lines) > MAX_LINES_INSIDE_FIGURE) { + return; + } + run.forEach(op -> result.add(op.ordinal())); + } + + /** How many text lines sit within the region a vector cluster covers. */ + private static int overlappingLines(BBox extent, List lines) { + int count = 0; + for (TextLineInfo line : lines) { + BBox box = line.bbox(); + boolean inside = + box.x0() >= extent.x0() - 2 + && box.x1() <= extent.x1() + 2 + && box.y0() >= extent.y0() - 2 + && box.y1() <= extent.y1() + 2; + if (inside) { + count++; + } + } + return count; + } + + // --- Post-processing --------------------------------------------------- + + /** + * Rewrites heading levels so no level is skipped, which PDF/UA-1 clause 7.4 requires. A + * document that jumps H1 to H3 is remapped to H1, H2 while preserving relative depth. + */ + static void normaliseHeadingLevels(DocumentStructure structure) { + List headings = new ArrayList<>(); + structure.visit( + block -> { + if (block.getType().isHeading()) { + headings.add(block); + } + }); + int previous = 0; + for (StructBlock heading : headings) { + int level = heading.getType().headingLevel(); + int adjusted = level > previous + 1 ? previous + 1 : level; + heading.setType(StructType.heading(adjusted)); + previous = adjusted; + } + } + + /** Uses the first top-level heading as the title when the document has no metadata title. */ + private static String deriveTitle(DocumentStructure structure) { + for (StructBlock block : structure.getBlocks()) { + if (block.getType().isHeading() && !block.getText().isBlank()) { + return block.getText().strip(); + } + } + return null; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/MarkableOp.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/MarkableOp.java new file mode 100644 index 0000000000..c48319260f --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/MarkableOp.java @@ -0,0 +1,44 @@ +package stirling.software.proprietary.pdf.ua; + +/** + * One operator in a page content stream that may be wrapped in a marked-content sequence. The + * ordinal counts only markable operators, joining text extraction to token rewriting. + */ +public record MarkableOp(int ordinal, Kind kind, BBox bbox, String resourceName) { + + public enum Kind { + /** Tj, TJ, ' or " */ + TEXT, + /** Do referencing an image XObject */ + IMAGE, + /** Do referencing a form XObject */ + FORM, + /** BI ... ID ... EI */ + INLINE_IMAGE, + /** A path-painting or shading operator: rules, borders, fills, logos */ + VECTOR; + + public boolean isGraphic() { + return this == IMAGE || this == INLINE_IMAGE; + } + } + + /** + * Operator names counted as markable; both passes must agree on this set. Path painting is + * included because clause 7.1 needs visible rules and borders tagged or artifacted. + */ + public static boolean isMarkableOperator(String name) { + return switch (name) { + case "Tj", "TJ", "'", "\"", "Do", "BI" -> true; + default -> isPathPainting(name); + }; + } + + /** Painting operators only: {@code n} ends a path without marking the page. */ + public static boolean isPathPainting(String name) { + return switch (name) { + case "S", "s", "f", "F", "f*", "B", "B*", "b", "b*", "sh" -> true; + default -> false; + }; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/MarkedContentInjector.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/MarkedContentInjector.java new file mode 100644 index 0000000000..c9d6330a60 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/MarkedContentInjector.java @@ -0,0 +1,284 @@ +package stirling.software.proprietary.pdf.ua; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.pdfbox.contentstream.operator.Operator; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSInteger; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdfparser.PDFStreamParser; +import org.apache.pdfbox.pdfwriter.ContentStreamWriter; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDStream; + +import lombok.extern.slf4j.Slf4j; + +/** + * Rewrites a page stream so every markable operator sits inside a marked-content sequence: claimed + * content gets an MCID, everything else /Artifact, satisfying PDF/UA-1 clause 7.1 by construction. + */ +@Slf4j +public class MarkedContentInjector { + + private static final COSName ARTIFACT = COSName.getPDFName("Artifact"); + private static final COSName MCID = COSName.getPDFName("MCID"); + private static final COSName ACTUAL_TEXT = COSName.getPDFName("ActualText"); + private static final COSName ALT = COSName.getPDFName("Alt"); + + /** Operators that force an open sequence to close so nesting stays legal. */ + private static boolean isBoundary(String name) { + return "BT".equals(name) || "ET".equals(name) || "q".equals(name) || "Q".equals(name); + } + + private static boolean isMarkedContentOperator(String name) { + return "BDC".equals(name) || "BMC".equals(name) || "EMC".equals(name); + } + + /** + * Path-construction operators; ISO 32000-1 forbids marked content inside a path object, so a + * sequence wrapping a fill or stroke must open before the path starts. + */ + private static boolean isPathConstruction(String name) { + return switch (name) { + case "m", "l", "c", "v", "y", "h", "re" -> true; + default -> false; + }; + } + + private static boolean opensMarkedContent(String name) { + return "BDC".equals(name) || "BMC".equals(name); + } + + /** + * True for an optional-content sequence; stripping an {@code /OC} wrapper would make hidden + * layers such as watermarks or redaction overlays visible. + */ + private static boolean isOptionalContent(String name, List operands) { + return opensMarkedContent(name) + && !operands.isEmpty() + && operands.get(0) instanceof COSName tag + && "OC".equals(tag.getName()); + } + + /** + * True when a sequence supplies replacement text for its glyphs; dropping it leaves a screen + * reader with the font's own mapping, which for a ligature says nothing useful. + */ + private static boolean carriesReplacementText(String name, List operands) { + if (!opensMarkedContent(name)) { + return false; + } + for (COSBase operand : operands) { + if (operand instanceof COSDictionary properties + && (properties.containsKey(ACTUAL_TEXT) + || properties.containsKey(ALT) + || properties.containsKey(COSName.E))) { + return true; + } + } + return false; + } + + /** The source's own ids mean nothing once the tree is rebuilt, so they are dropped. */ + private static void stripStaleMcid(List operands) { + for (COSBase operand : operands) { + if (operand instanceof COSDictionary properties) { + properties.removeItem(MCID); + } + } + } + + /** Wraps every markable operator on the page; returns the next unused marked content id. */ + public int inject( + PDDocument document, + PDPage page, + List blocks, + int nextMcid, + boolean stripExisting) + throws IOException { + + Map owners = ownersByOrdinal(blocks); + List tokens = parse(page); + List output = new ArrayList<>(tokens.size() + owners.size() * 4); + + List operands = new ArrayList<>(); + // Tracks, for each surviving source sequence, whether its closer should be kept. + Deque keptSequences = new ArrayDeque<>(); + StructBlock openBlock = null; + boolean open = false; + int ordinal = -1; + int mcid = nextMcid; + int pathStart = -1; + + for (Object token : tokens) { + if (!(token instanceof Operator operator)) { + operands.add((COSBase) token); + continue; + } + String name = operator.getName(); + + if (stripExisting && isMarkedContentOperator(name)) { + boolean keep; + if (opensMarkedContent(name)) { + keep = + isOptionalContent(name, operands) + || carriesReplacementText(name, operands); + if (keep) { + stripStaleMcid(operands); + } + keptSequences.push(keep); + } else { + // A closer is kept exactly when its matching opener was. + keep = !keptSequences.isEmpty() && keptSequences.pop(); + } + if (!keep) { + operands.clear(); + continue; + } + // Close our own sequence first so the two never interleave illegally. + if (open) { + output.add(Operator.getOperator("EMC")); + open = false; + openBlock = null; + } + output.addAll(operands); + output.add(operator); + operands.clear(); + continue; + } + + if (isBoundary(name) && open) { + output.add(Operator.getOperator("EMC")); + open = false; + openBlock = null; + } + + // Remember where the current path object began so a sequence wrapping its painting + // operator can be opened before it rather than inside it. + if (isPathConstruction(name)) { + if (pathStart < 0) { + pathStart = output.size(); + } + } else if (!MarkableOp.isPathPainting(name) && !"n".equals(name)) { + pathStart = -1; + } + + if (MarkableOp.isMarkableOperator(name)) { + ordinal++; + StructBlock owner = owners.get(ordinal); + if (!open || owner != openBlock) { + boolean insidePath = MarkableOp.isPathPainting(name) && pathStart >= 0; + if (open) { + // Close before the path began, so the EMC also stays outside the path. + output.add( + insidePath ? pathStart : output.size(), + Operator.getOperator("EMC")); + if (insidePath) { + pathStart++; + } + } + int at = insidePath ? pathStart : output.size(); + mcid = openSequenceAt(output, at, owner, mcid); + open = true; + openBlock = owner; + } + } + + output.addAll(operands); + output.add(operator); + operands.clear(); + + if (MarkableOp.isPathPainting(name) || "n".equals(name)) { + pathStart = -1; + } + } + + if (open) { + output.add(Operator.getOperator("EMC")); + } + + write(document, page, output); + return mcid; + } + + /** Emits the opening BDC/BMC at a given position and records the id on the owning block. */ + private int openSequenceAt(List output, int at, StructBlock owner, int mcid) { + List opening = new ArrayList<>(3); + if (owner == null) { + opening.add(ARTIFACT); + opening.add(Operator.getOperator("BMC")); + } else if (owner.isArtifact()) { + COSDictionary properties = new COSDictionary(); + if (owner.getArtifactType() != null) { + properties.setName(COSName.TYPE, owner.getArtifactType().subtype()); + } + opening.add(ARTIFACT); + opening.add(properties); + opening.add(Operator.getOperator("BDC")); + } else { + COSDictionary properties = new COSDictionary(); + properties.setItem(MCID, COSInteger.get(mcid)); + opening.add(COSName.getPDFName(owner.getType().tag())); + opening.add(properties); + opening.add(Operator.getOperator("BDC")); + owner.getMcids().add(mcid); + mcid++; + } + output.addAll(at, opening); + return mcid; + } + + /** + * Maps each claimed ordinal to its block. Overlapping claims are dropped rather than merged: + * two structure elements sharing content would make the reading order ambiguous. + */ + static Map ownersByOrdinal(List blocks) { + Map owners = new HashMap<>(); + for (StructBlock block : blocks) { + block.visit( + node -> { + for (StructBlock.OrdinalRange range : node.getRanges()) { + for (int i = range.start(); i <= range.end(); i++) { + StructBlock existing = owners.putIfAbsent(i, node); + if (existing != null && existing != node) { + log.debug( + "Ordinal {} claimed by both {} and {}; keeping the first", + i, + existing, + node); + } + } + } + }); + } + return owners; + } + + private static List parse(PDPage page) throws IOException { + PDFStreamParser parser = new PDFStreamParser(page); + List tokens = new ArrayList<>(); + Object token; + while ((token = parser.parseNextToken()) != null) { + tokens.add(token); + } + return tokens; + } + + private static void write(PDDocument document, PDPage page, List tokens) + throws IOException { + PDStream stream = new PDStream(document); + try (OutputStream out = stream.createOutputStream(COSName.FLATE_DECODE)) { + new ContentStreamWriter(out).writeTokens(tokens); + } + page.setContents(stream); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PageContent.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PageContent.java new file mode 100644 index 0000000000..6c74212621 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PageContent.java @@ -0,0 +1,33 @@ +package stirling.software.proprietary.pdf.ua; + +import java.util.List; + +/** + * Everything the layout analyser needs about one page. carriesTextSemantics: existing marked + * content has ActualText/Alt/expansion a rebuild would discard. linesDropped: text became + * artifacts. + */ +public record PageContent( + int pageIndex, + List lines, + List ops, + int markableCount, + boolean preExistingMarkedContent, + boolean carriesTextSemantics, + boolean linesDropped, + BBox mediaBox) { + + public boolean hasText() { + return lines.stream().anyMatch(line -> !line.isBlank()); + } + + /** Markable operators that draw graphics rather than text. */ + public List graphics() { + return ops.stream().filter(op -> op.kind().isGraphic()).toList(); + } + + /** Form XObject invocations, which are tagged as a unit because their text is opaque here. */ + public List forms() { + return ops.stream().filter(op -> op.kind() == MarkableOp.Kind.FORM).toList(); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaIdentificationSchema.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaIdentificationSchema.java new file mode 100644 index 0000000000..d37c2569c9 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaIdentificationSchema.java @@ -0,0 +1,47 @@ +package stirling.software.proprietary.pdf.ua; + +import org.apache.xmpbox.XMPMetadata; +import org.apache.xmpbox.schema.XMPSchema; +import org.apache.xmpbox.type.IntegerType; +import org.apache.xmpbox.type.StructuredType; + +/** + * The {@code pdfuaid} XMP conformance schema, which XMPBox does not ship. Only write it once + * validation has passed - it is a compliance claim. + */ +@StructuredType( + preferedPrefix = PdfUaIdentificationSchema.PREFERRED_PREFIX, + namespace = PdfUaIdentificationSchema.NAMESPACE) +public class PdfUaIdentificationSchema extends XMPSchema { + + public static final String PREFERRED_PREFIX = "pdfuaid"; + public static final String NAMESPACE = "http://www.aiim.org/pdfua/ns/id/"; + + public static final String PART = "part"; + public static final String REV = "rev"; + + public PdfUaIdentificationSchema(XMPMetadata metadata) { + super(metadata); + } + + public PdfUaIdentificationSchema(XMPMetadata metadata, String prefix) { + super(metadata, prefix); + } + + /** Sets {@code pdfuaid:part}, the conformance level (1 or 2). */ + public void setPart(int part) { + addProperty(new IntegerType(getMetadata(), getNamespace(), getPrefix(), PART, part)); + } + + /** Sets {@code pdfuaid:rev}, the four-digit revision year used by PDF/UA-2. */ + public void setRevision(int year) { + addProperty(new IntegerType(getMetadata(), getNamespace(), getPrefix(), REV, year)); + } + + public Integer getPart() { + if (getProperty(PART) instanceof IntegerType part) { + return part.getValue(); + } + return null; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaMetadataWriter.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaMetadataWriter.java new file mode 100644 index 0000000000..7f58654c87 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaMetadataWriter.java @@ -0,0 +1,224 @@ +package stirling.software.proprietary.pdf.ua; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PDDocumentInformation; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences; +import org.apache.xmpbox.XMPMetadata; +import org.apache.xmpbox.schema.DublinCoreSchema; +import org.apache.xmpbox.schema.XMPSchema; +import org.apache.xmpbox.xml.DomXmpParser; +import org.apache.xmpbox.xml.XmpSerializer; + +import lombok.extern.slf4j.Slf4j; + +/** Applies the document-level PDF/UA requirements: title, language, tab order, declaration. */ +@Slf4j +public class PdfUaMetadataWriter { + + private static final COSName TABS = COSName.getPDFName("Tabs"); + private static final COSName SUSPECTS = COSName.getPDFName("Suspects"); + + /** + * Applies everything except the conformance declaration. Clause 7.1 requires a title, so a + * blank one falls back to the existing metadata title. + */ + public List applyDocumentRequirements( + PDDocument document, String title, String language, PdfUaProfile profile) + throws IOException { + return applyDocumentRequirements(document, title, language, profile, false); + } + + public List applyDocumentRequirements( + PDDocument document, + String title, + String language, + PdfUaProfile profile, + boolean preserveVersion) + throws IOException { + + List warnings = new ArrayList<>(); + PDDocumentCatalog catalog = document.getDocumentCatalog(); + + if (language != null && !language.isBlank()) { + catalog.setLanguage(language); + } + + String effectiveTitle = resolveTitle(document, title); + if (effectiveTitle != null) { + PDDocumentInformation info = document.getDocumentInformation(); + info.setTitle(effectiveTitle); + document.setDocumentInformation(info); + } + + // Without this a viewer shows the filename instead of the title, which defeats the point. + PDViewerPreferences preferences = catalog.getViewerPreferences(); + if (preferences == null) { + preferences = new PDViewerPreferences(catalog.getCOSObject()); + } + preferences.setDisplayDocTitle(true); + catalog.setViewerPreferences(preferences); + + // Clause 7.18.1: every page needs an explicit tab order. + for (PDPage page : document.getPages()) { + page.getCOSObject().setName(TABS, "S"); + } + + // A structure tree flagged as suspect is not conforming. + if (catalog.getMarkInfo() != null) { + catalog.getMarkInfo().getCOSObject().removeItem(SUSPECTS); + } + + if (!preserveVersion && document.getVersion() < profile.pdfVersion()) { + document.setVersion(profile.pdfVersion()); + } + + warnings.addAll(describeFormFields(document)); + writeXmp(document, effectiveTitle, language, null); + return warnings; + } + + /** + * Gives every form field the {@code /TU} description clause 7.18.1 requires, reusing its + * authored partial name. Unnamed fields are reported, never given a useless placeholder. + */ + private static List describeFormFields(PDDocument document) { + List warnings = new ArrayList<>(); + PDAcroForm form = document.getDocumentCatalog().getAcroForm(); + if (form == null) { + return warnings; + } + int unnamed = 0; + for (PDField field : form.getFieldTree()) { + String existing = field.getAlternateFieldName(); + if (existing != null && !existing.isBlank()) { + continue; + } + String partialName = field.getPartialName(); + if (partialName == null || partialName.isBlank()) { + unnamed++; + continue; + } + field.setAlternateFieldName(partialName); + } + if (unnamed > 0) { + warnings.add( + unnamed + + " form field(s) have neither a description nor a name, so no tooltip" + + " could be derived. Add one for each before claiming conformance."); + } + return warnings; + } + + /** + * Strips the {@code pdfuaid} declaration when validation fails after it was written, so the + * returned file does not assert conformance it lacks. + */ + public void removeConformanceDeclaration(PDDocument document) throws IOException { + PDDocumentCatalog catalog = document.getDocumentCatalog(); + XMPMetadata metadata = loadOrCreate(catalog); + XMPSchema identification = metadata.getSchema(PdfUaIdentificationSchema.NAMESPACE); + if (identification == null) { + return; + } + metadata.removeSchema(identification); + serialiseInto(document, metadata); + } + + /** Writes the {@code pdfuaid:part} declaration. Only call this after validation has passed. */ + public void declareConformance(PDDocument document, PdfUaProfile profile) throws IOException { + writeXmp(document, resolveTitle(document, null), documentLanguage(document), profile); + } + + private String resolveTitle(PDDocument document, String preferred) { + if (preferred != null && !preferred.isBlank()) { + return preferred.strip(); + } + String existing = document.getDocumentInformation().getTitle(); + return existing != null && !existing.isBlank() ? existing.strip() : null; + } + + private static String documentLanguage(PDDocument document) { + return document.getDocumentCatalog().getLanguage(); + } + + /** + * Rewrites the XMP packet, preserving what was there. A malformed packet is replaced, since an + * unparseable one fails validation on its own. + */ + private void writeXmp(PDDocument document, String title, String language, PdfUaProfile profile) + throws IOException { + + PDDocumentCatalog catalog = document.getDocumentCatalog(); + XMPMetadata metadata = loadOrCreate(catalog); + + if (title != null) { + DublinCoreSchema dublinCore = metadata.getDublinCoreSchema(); + if (dublinCore == null) { + dublinCore = metadata.createAndAddDublinCoreSchema(); + } + dublinCore.setTitle(title); + if (language != null + && !language.isBlank() + && (dublinCore.getLanguages() == null + || !dublinCore.getLanguages().contains(language))) { + dublinCore.addLanguage(language); + } + } + + if (profile != null) { + // Re-converting an already-declared file must not leave two pdfuaid schemas. + XMPSchema stale = metadata.getSchema(PdfUaIdentificationSchema.NAMESPACE); + if (stale != null) { + metadata.removeSchema(stale); + } + PdfUaIdentificationSchema identification = new PdfUaIdentificationSchema(metadata); + identification.setPart(profile.part()); + if (profile.revision() > 0) { + identification.setRevision(profile.revision()); + } + metadata.addSchema(identification); + } + + serialiseInto(document, metadata); + } + + private static void serialiseInto(PDDocument document, XMPMetadata metadata) + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + new XmpSerializer().serialize(metadata, out, true); + } catch (javax.xml.transform.TransformerException e) { + throw new IOException("Could not serialise XMP metadata", e); + } + PDMetadata pdMetadata = new PDMetadata(document); + pdMetadata.importXMPMetadata(out.toByteArray()); + document.getDocumentCatalog().setMetadata(pdMetadata); + } + + private XMPMetadata loadOrCreate(PDDocumentCatalog catalog) { + PDMetadata existing = catalog.getMetadata(); + if (existing != null) { + try { + DomXmpParser parser = new DomXmpParser(); + // Strict parsing rejects pdfuaid, silently discarding a packet we just wrote. + parser.setStrictParsing(false); + return parser.parse(new ByteArrayInputStream(existing.toByteArray())); + } catch (Exception e) { + log.debug("Replacing unparseable XMP packet: {}", e.getMessage()); + } + } + return XMPMetadata.createXMPMetadata(); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaProfile.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaProfile.java new file mode 100644 index 0000000000..9523f5c790 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaProfile.java @@ -0,0 +1,47 @@ +package stirling.software.proprietary.pdf.ua; + +/** The PDF/UA conformance level a conversion targets. */ +public enum PdfUaProfile { + /** ISO 14289-1, layered on PDF 1.7. */ + UA1(1, 1.7f, 0), + /** ISO 14289-2: needs PDF 2.0, namespaced structure types and a revision year. */ + UA2(2, 2.0f, 2024); + + private final int part; + private final float pdfVersion; + private final int revision; + + PdfUaProfile(int part, float pdfVersion, int revision) { + this.part = part; + this.pdfVersion = pdfVersion; + this.revision = revision; + } + + public int part() { + return part; + } + + public float pdfVersion() { + return pdfVersion; + } + + /** The {@code pdfuaid:rev} year, or 0 when the profile does not use one. */ + public int revision() { + return revision; + } + + public String displayName() { + return "PDF/UA-" + part; + } + + public static PdfUaProfile fromRequest(String value) { + if (value == null || value.isBlank()) { + return UA1; + } + String normalised = value.trim().toLowerCase().replace("/", "").replace("-", ""); + return switch (normalised) { + case "ua2", "pdfua2", "2" -> UA2; + default -> UA1; + }; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaTagger.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaTagger.java new file mode 100644 index 0000000000..7f5dcc3739 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/PdfUaTagger.java @@ -0,0 +1,303 @@ +package stirling.software.proprietary.pdf.ua; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; + +import lombok.extern.slf4j.Slf4j; + +/** + * Tags an untagged PDF and applies the document-level PDF/UA requirements. Content must be marked + * before the tree can reference it, and conformance is declared elsewhere, only after validation. + */ +@Slf4j +public class PdfUaTagger { + + private final TaggedContentExtractor extractor = new TaggedContentExtractor(); + private final LayoutAnalyzer analyzer = new LayoutAnalyzer(); + private final MarkedContentInjector injector = new MarkedContentInjector(); + private final PdfUaMetadataWriter metadataWriter = new PdfUaMetadataWriter(); + + public TaggingResult tag(PDDocument document, TaggingOptions options) throws IOException { + boolean alreadyTagged = hasUsableStructureTree(document); + boolean rebuild = + switch (options.getExistingTags()) { + case KEEP -> false; + case REBUILD -> true; + case AUTO -> !alreadyTagged; + }; + + List languageWarnings = new ArrayList<>(); + String language = resolveLanguage(document, options, languageWarnings); + + if (!rebuild) { + log.info("Keeping existing structure tree; applying document requirements only"); + DocumentStructure kept = new DocumentStructure(); + languageWarnings.forEach(kept::warn); + metadataWriter + .applyDocumentRequirements( + document, + options.getTitle(), + language, + options.getProfile(), + options.isPreservePdfVersion()) + .forEach(kept::warn); + return new TaggingResult(kept, false); + } + + // Types the old tree carried, so a rebuild that cannot reproduce them can say so. Font + // embedding may already have deleted the tree, so fall back to what the source had. + Set discardedTypes = + alreadyTagged + ? structureTypes(document) + : options.getSourceFacts().structureTypes(); + + if (alreadyTagged) { + stripStructure(document); + } + + List pages = extractor.extract(document); + DocumentStructure structure = analyzer.analyse(pages); + structure.setLanguage(language); + languageWarnings.forEach(structure::warn); + applyFigurePolicy(structure, options); + + if (structure.isEmpty()) { + structure.warn( + "No taggable content was found; the document may be a scan with no text layer."); + } + + injectMarkedContent(document, structure, pages); + new StructTreeWriter().write(document, structure, options.getProfile()); + // Losing the tree to the embedder is a different problem from a requested rebuild, and + // the advice that helps differs too, so tell them apart. + boolean lostToEmbedder = !alreadyTagged && options.getSourceFacts().hasUsableTree(); + warnAboutFlattenedStructure( + discardedTypes, structureTypes(document), structure, lostToEmbedder); + + String title = resolveTitle(options, structure); + if (title == null) { + structure.warn( + "No document title could be derived. PDF/UA requires one, so supply a title."); + } + metadataWriter + .applyDocumentRequirements( + document, + title, + language, + options.getProfile(), + options.isPreservePdfVersion()) + .forEach(structure::warn); + + return new TaggingResult(structure, true); + } + + /** + * Keeps the language the document already declares. Overwriting it relabels, say, a French file + * as English, and no validator can catch that. + */ + private static String resolveLanguage( + PDDocument document, TaggingOptions options, List warnings) { + String existing = document.getDocumentCatalog().getLanguage(); + if (existing == null || existing.isBlank()) { + // Font embedding discards /Lang, so without this a rewritten French document would + // silently take the caller's default language. + existing = options.getSourceFacts().language(); + } + String requested = options.getLanguage(); + if (existing == null || existing.isBlank() || options.isOverrideLanguage()) { + return requested; + } + if (requested != null && !requested.isBlank() && !requested.equalsIgnoreCase(existing)) { + warnings.add( + "The document already declares its language as '" + + existing + + "', so the requested '" + + requested + + "' was ignored. Ask to override the language to change it."); + } + return existing; + } + + /** Explicit title first, then the first heading, then the caller's fallback. */ + private static String resolveTitle(TaggingOptions options, DocumentStructure structure) { + for (String candidate : + new String[] { + options.getTitle(), structure.getTitle(), options.getFallbackTitle() + }) { + if (candidate != null && !candidate.isBlank()) { + return candidate.strip(); + } + } + return null; + } + + /** Writes the conformance declaration. Separate from tagging so validation can gate it. */ + public void declareConformance(PDDocument document, PdfUaProfile profile) throws IOException { + metadataWriter.declareConformance(document, profile); + } + + /** Withdraws the conformance claim, for a document that turned out not to validate. */ + public void withdrawConformance(PDDocument document) throws IOException { + metadataWriter.removeConformanceDeclaration(document); + } + + /** Wraps content page by page; marked content ids restart on each page. */ + private void injectMarkedContent( + PDDocument document, DocumentStructure structure, List pages) + throws IOException { + Map markableCounts = new LinkedHashMap<>(); + pages.forEach(page -> markableCounts.put(page.pageIndex(), page.markableCount())); + Map> byPage = new LinkedHashMap<>(); + for (StructBlock block : structure.getBlocks()) { + byPage.computeIfAbsent(block.getPageIndex(), k -> new ArrayList<>()).add(block); + } + for (int pageIndex = 0; pageIndex < document.getNumberOfPages(); pageIndex++) { + List blocks = byPage.getOrDefault(pageIndex, List.of()); + // Nothing to wrap, and rewriting costs a parse and recompress for an identical stream. + if (blocks.isEmpty() && markableCounts.getOrDefault(pageIndex, 0) == 0) { + continue; + } + injector.inject(document, document.getPage(pageIndex), blocks, 0, true); + } + } + + /** Applies alt text supplied by the caller, or demotes images to artifacts on request. */ + private static void applyFigurePolicy(DocumentStructure structure, TaggingOptions options) { + int[] suppressed = {0}; + structure.visit( + block -> { + if (block.getType() != StructType.FIGURE) { + return; + } + if (options.getFigurePolicy() == TaggingOptions.FigurePolicy.MARK_DECORATIVE) { + block.setType(StructType.ARTIFACT); + block.setArtifactType(ArtifactType.LAYOUT); + suppressed[0]++; + return; + } + int ordinal = + block.getRanges().isEmpty() ? -1 : block.getRanges().get(0).start(); + String alt = options.altTextFor(block.getPageIndex(), ordinal); + if (alt != null && !alt.isBlank()) { + block.setAlt(alt); + } + }); + // Marking images decorative validates by hiding content, so never report it as clean. + if (suppressed[0] > 0) { + structure.warn( + suppressed[0] + + " image(s) were marked as decoration and are now hidden from" + + " assistive technology. Confirm none of them carried meaning."); + } + int missing = structure.figuresWithoutAlt().size(); + if (missing > 0) { + structure.warn( + missing + + " figure(s) have no alternative description. PDF/UA requires one for" + + " every image that carries meaning."); + } + } + + /** + * A tree is only worth keeping when wired up: kids, a parent tree, and a marked catalog. + * Keeping one that fails any of those leaves the document permanently unfixable. + */ + public static boolean hasUsableStructureTree(PDDocument document) { + PDDocumentCatalog catalog = document.getDocumentCatalog(); + PDStructureTreeRoot root = catalog.getStructureTreeRoot(); + if (root == null) { + return false; + } + try { + boolean hasKids = root.getKids() != null && !root.getKids().isEmpty(); + boolean hasParentTree = root.getParentTree() != null; + boolean marked = catalog.getMarkInfo() != null && catalog.getMarkInfo().isMarked(); + return hasKids && hasParentTree && marked; + } catch (RuntimeException e) { + log.debug("Unreadable structure tree, treating as absent: {}", e.getMessage()); + return false; + } + } + + /** + * A rebuild derives structure from layout, so semantics the old tree carried can vanish - a + * table becomes loose paragraphs. Validators cannot see that loss, so it has to be reported. + */ + private static void warnAboutFlattenedStructure( + Set before, + Set after, + DocumentStructure structure, + boolean lostToEmbedder) { + List lost = + MEANINGFUL_TYPES.stream() + .filter(type -> before.contains(type) && !after.contains(type)) + .toList(); + if (lost.isEmpty()) { + return; + } + // Keeping the tags cannot help once the embedder has deleted them, so do not suggest it. + String remedy = + lostToEmbedder + ? " Embedding the missing fonts rewrote the document and deleted its" + + " original tags. Turn off font embedding to keep them." + : " Keep the existing tags instead to preserve it."; + structure.warn( + "Rebuilding the tags could not reproduce " + + String.join(", ", lost) + + " structure, so that content is now plain paragraphs." + + remedy); + } + + /** Structure whose loss changes what a screen reader conveys, not just how it is nested. */ + private static final List MEANINGFUL_TYPES = + List.of("Table", "TH", "Formula", "L", "LI", "TOC", "Note"); + + private static Set structureTypes(PDDocument document) { + Set types = new HashSet<>(); + try { + PDStructureTreeRoot root = document.getDocumentCatalog().getStructureTreeRoot(); + if (root != null) { + collectTypes(root.getKids(), types, 0); + } + } catch (RuntimeException e) { + log.debug("Could not read structure types: {}", e.getMessage()); + } + return types; + } + + private static void collectTypes(Object node, Set types, int depth) { + // Structure trees can be deep or, in damaged files, cyclic; cap rather than overflow. + if (node == null || depth > 64) { + return; + } + if (node instanceof List list) { + list.forEach(child -> collectTypes(child, types, depth + 1)); + } else if (node instanceof PDStructureElement element) { + types.add(element.getStructureType()); + collectTypes(element.getKids(), types, depth + 1); + } + } + + private static void stripStructure(PDDocument document) { + PDDocumentCatalog catalog = document.getDocumentCatalog(); + catalog.getCOSObject().removeItem(COSName.getPDFName("StructTreeRoot")); + catalog.getCOSObject().removeItem(COSName.getPDFName("MarkInfo")); + document.getPages() + .forEach( + page -> + page.getCOSObject() + .removeItem(COSName.getPDFName("StructParents"))); + log.info("Removed existing structure tree before rebuilding"); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/SourceFacts.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/SourceFacts.java new file mode 100644 index 0000000000..4f058d6c48 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/SourceFacts.java @@ -0,0 +1,59 @@ +package stirling.software.proprietary.pdf.ua; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; + +import lombok.extern.slf4j.Slf4j; + +/** + * What the document said about itself before anything rewrote it. Font embedding shells out to + * Ghostscript, which returns a file with no structure tree, no {@code /Lang} and no XMP, so a + * tagger reading the rewritten document sees an untagged, language-less file and cannot tell that + * anything was lost. These facts are captured from the original and carried past that stage. + * + * @param language the catalog {@code /Lang} the author declared, or null + * @param structureTypes every structure element type the original tree contained + * @param hasUsableTree whether the original had a structure tree worth preserving + */ +@Slf4j +public record SourceFacts(String language, Set structureTypes, boolean hasUsableTree) { + + private static final int MAX_DEPTH = 64; + + /** Facts for a document nothing has rewritten, used when font embedding did not run. */ + public static final SourceFacts NONE = new SourceFacts(null, Set.of(), false); + + public static SourceFacts of(PDDocument document) { + String language = null; + Set types = new HashSet<>(); + boolean usable = false; + try { + language = document.getDocumentCatalog().getLanguage(); + usable = PdfUaTagger.hasUsableStructureTree(document); + PDStructureTreeRoot root = document.getDocumentCatalog().getStructureTreeRoot(); + if (root != null) { + collect(root.getKids(), types, 0); + } + } catch (RuntimeException e) { + log.debug("Could not read source facts: {}", e.getMessage()); + } + return new SourceFacts(language, Set.copyOf(types), usable); + } + + private static void collect(Object node, Set types, int depth) { + // Damaged files can present a cyclic tree; cap rather than overflow the stack. + if (node == null || depth > MAX_DEPTH) { + return; + } + if (node instanceof java.util.List list) { + list.forEach(child -> collect(child, types, depth + 1)); + } else if (node instanceof PDStructureElement element) { + types.add(element.getStructureType()); + collect(element.getKids(), types, depth + 1); + } + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructBlock.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructBlock.java new file mode 100644 index 0000000000..d2f3e452b1 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructBlock.java @@ -0,0 +1,134 @@ +package stirling.software.proprietary.pdf.ua; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import lombok.Getter; +import lombok.Setter; + +/** + * One node of the derived logical structure: either page content (ranges of markable operator + * ordinals) or child blocks. Containers with no content are pruned before serialisation. + */ +@Getter +@Setter +public class StructBlock { + + /** A contiguous, inclusive run of markable operator ordinals within one page stream. */ + public record OrdinalRange(int start, int end) { + public boolean contains(int ordinal) { + return ordinal >= start && ordinal <= end; + } + + public int size() { + return end - start + 1; + } + } + + private StructType type; + private ArtifactType artifactType; + private int pageIndex; + private BBox bbox = BBox.EMPTY; + private String text = ""; + + private final List ranges = new ArrayList<>(); + private final List children = new ArrayList<>(); + + /** {@code /Alt} - required on Figure and Formula for PDF/UA. */ + private String alt; + + /** {@code /ActualText} - replacement text for content whose glyphs do not spell the word. */ + private String actualText; + + /** {@code /Lang} - set only where it differs from the document default. */ + private String lang; + + /** {@code /Scope} on a TH: Row, Column or Both. */ + private String scope; + + /** {@code /ListNumbering} on an L. */ + private String listNumbering; + + /** Unique {@code /ID}, required on Note and FENote elements. */ + private String id; + + /** + * Marked content ids assigned during injection; one block yields several when split, since a + * sequence must nest inside BT/ET and q/Q rather than straddle them. + */ + private final List mcids = new ArrayList<>(); + + /** True when the source content was already inside a marked-content sequence. */ + private boolean preMarked; + + public StructBlock(StructType type, int pageIndex) { + this.type = type; + this.pageIndex = pageIndex; + } + + public static StructBlock artifact(ArtifactType artifactType, int pageIndex) { + StructBlock block = new StructBlock(StructType.ARTIFACT, pageIndex); + block.artifactType = artifactType; + return block; + } + + public StructBlock addChild(StructBlock child) { + children.add(child); + return this; + } + + public StructBlock addRange(int start, int end) { + ranges.add(new OrdinalRange(start, end)); + return this; + } + + public boolean isArtifact() { + return type == StructType.ARTIFACT; + } + + /** Depth-first walk over this block and all descendants. */ + public void visit(Consumer visitor) { + visitor.accept(this); + for (StructBlock child : children) { + child.visit(visitor); + } + } + + /** Total number of ordinals owned by this block and its descendants. */ + public int contentCount() { + int total = ranges.stream().mapToInt(OrdinalRange::size).sum(); + for (StructBlock child : children) { + total += child.contentCount(); + } + return total; + } + + /** Concatenated text of this block and its descendants, in tree order. */ + public String collectText() { + StringBuilder sb = new StringBuilder(); + visit( + block -> { + if (!block.text.isBlank()) { + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(block.text.strip()); + } + }); + return sb.toString(); + } + + @Override + public String toString() { + return type.tag() + + (artifactType != null ? "[" + artifactType.subtype() + "]" : "") + + "(p" + + pageIndex + + ", " + + ranges.size() + + " ranges, " + + children.size() + + " kids)"; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructTreeWriter.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructTreeWriter.java new file mode 100644 index 0000000000..f6b5d89b4c --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructTreeWriter.java @@ -0,0 +1,295 @@ +package stirling.software.proprietary.pdf.ua; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSInteger; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDNumberTreeNode; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDObjectReference; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; +import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDListAttributeObject; +import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDTableAttributeObject; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; + +import lombok.extern.slf4j.Slf4j; + +/** + * Serialises a {@link DocumentStructure} into a PDF structure tree. Must run after {@link + * MarkedContentInjector}, which assigns the marked content ids this writer references. + */ +@Slf4j +public class StructTreeWriter { + + private static final COSName STRUCT_PARENT = COSName.getPDFName("StructParent"); + private static final COSName NUMS = COSName.getPDFName("Nums"); + private static final String PDF2_STANDARD_NAMESPACE = "http://iso.org/pdf2/ssn"; + + /** Per-page marked content id to owning element, built while walking the tree. */ + private final Map> mcidOwners = new LinkedHashMap<>(); + + private COSDictionary standardNamespace; + private int nextParentKey; + + public void write(PDDocument document, DocumentStructure structure, PdfUaProfile profile) + throws IOException { + PDStructureTreeRoot root = new PDStructureTreeRoot(); + PDStructureElement documentElement = + new PDStructureElement(StructType.DOCUMENT.tag(), root); + if (structure.getLanguage() != null) { + documentElement.setLanguage(structure.getLanguage()); + } + if (profile == PdfUaProfile.UA2) { + applyNamespace(documentElement, document); + } + + for (StructBlock block : structure.getBlocks()) { + if (block.isArtifact()) { + continue; + } + PDStructureElement child = buildElement(document, block, documentElement, profile); + if (child != null) { + documentElement.appendKid(child); + } + } + + root.appendKid(documentElement); + buildParentTree(document, root); + registerNamespaces(root); + + PDMarkInfo markInfo = new PDMarkInfo(); + markInfo.setMarked(true); + document.getDocumentCatalog().setMarkInfo(markInfo); + document.getDocumentCatalog().setStructureTreeRoot(root); + } + + /** Recursively builds an element, returning null when the block carries no content at all. */ + private PDStructureElement buildElement( + PDDocument document, + StructBlock block, + PDStructureElement parent, + PdfUaProfile profile) { + + // Prune on assigned MCIDs, not claimed ranges: form-XObject lines all resolve to one Do, + // and emitting the losers would announce empty paragraphs to a screen reader. + if (!carriesContent(block)) { + return null; + } + StructType type = effectiveType(block, profile); + PDStructureElement element = new PDStructureElement(type.tag(), parent); + PDPage page = document.getPage(block.getPageIndex()); + element.setPage(page); + + if (profile == PdfUaProfile.UA2) { + applyNamespace(element, document); + } + applyAttributes(block, element); + + for (int mcid : block.getMcids()) { + element.appendKid(mcid); + mcidOwners + .computeIfAbsent(block.getPageIndex(), k -> new LinkedHashMap<>()) + .put(mcid, element); + } + + for (StructBlock child : block.getChildren()) { + PDStructureElement childElement = buildElement(document, child, element, profile); + if (childElement != null) { + element.appendKid(childElement); + } + } + return element; + } + + /** True when this block, or something beneath it, was actually given marked content. */ + private static boolean carriesContent(StructBlock block) { + if (!block.getMcids().isEmpty()) { + return true; + } + return block.getChildren().stream().anyMatch(StructTreeWriter::carriesContent); + } + + /** PDF/UA-2 replaces Note with FENote for footnotes. */ + private static StructType effectiveType(StructBlock block, PdfUaProfile profile) { + if (profile == PdfUaProfile.UA2 && block.getType() == StructType.NOTE) { + return StructType.FENOTE; + } + return block.getType(); + } + + private static void applyAttributes(StructBlock block, PDStructureElement element) { + if (block.getAlt() != null && !block.getAlt().isBlank()) { + element.setAlternateDescription(block.getAlt()); + } + if (block.getActualText() != null && !block.getActualText().isBlank()) { + element.setActualText(block.getActualText()); + } + if (block.getLang() != null && !block.getLang().isBlank()) { + element.setLanguage(block.getLang()); + } + if (block.getId() != null && !block.getId().isBlank()) { + element.setElementIdentifier(block.getId()); + } + if (block.getScope() != null) { + PDTableAttributeObject table = new PDTableAttributeObject(); + table.setScope(block.getScope()); + element.addAttribute(table); + } + if (block.getListNumbering() != null) { + PDListAttributeObject list = new PDListAttributeObject(); + list.setListNumbering(block.getListNumbering()); + element.addAttribute(list); + } + } + + /** PDF/UA-2 requires every element to declare the standard structure namespace. */ + private void applyNamespace(PDStructureElement element, PDDocument document) { + element.getCOSObject().setItem(COSName.getPDFName("NS"), standardNamespace()); + } + + /** The PDF 2.0 standard structure namespace, created once per document. */ + private COSDictionary standardNamespace() { + if (standardNamespace == null) { + standardNamespace = new COSDictionary(); + standardNamespace.setName(COSName.TYPE, "Namespace"); + standardNamespace.setString(COSName.getPDFName("NS"), PDF2_STANDARD_NAMESPACE); + } + return standardNamespace; + } + + private void registerNamespaces(PDStructureTreeRoot root) { + if (standardNamespace == null) { + return; + } + COSArray namespaces = new COSArray(); + namespaces.add(standardNamespace); + root.getCOSObject().setItem(COSName.getPDFName("Namespaces"), namespaces); + } + + /** + * Builds {@code /ParentTree}: per page, an array indexed by marked content id keyed on {@code + * /StructParents}, plus one entry per annotation keyed on {@code /StructParent}. + */ + private void buildParentTree(PDDocument document, PDStructureTreeRoot root) { + COSArray nums = new COSArray(); + nextParentKey = 0; + + for (int pageIndex = 0; pageIndex < document.getNumberOfPages(); pageIndex++) { + Map owners = mcidOwners.get(pageIndex); + if (owners == null || owners.isEmpty()) { + continue; + } + PDPage page = document.getPage(pageIndex); + int key = nextParentKey++; + page.setStructParents(key); + + int maxMcid = owners.keySet().stream().mapToInt(Integer::intValue).max().orElse(-1); + COSArray entries = new COSArray(); + for (int mcid = 0; mcid <= maxMcid; mcid++) { + PDStructureElement owner = owners.get(mcid); + entries.add( + owner != null ? owner.getCOSObject() : org.apache.pdfbox.cos.COSNull.NULL); + } + nums.add(COSInteger.get(key)); + nums.add(entries); + } + + List annotationEntries = tagAnnotations(document, root); + for (int i = 0; i + 1 < annotationEntries.size(); i += 2) { + nums.add(annotationEntries.get(i)); + nums.add(annotationEntries.get(i + 1)); + } + + COSDictionary parentTreeDict = new COSDictionary(); + parentTreeDict.setItem(NUMS, nums); + root.setParentTree(new PDNumberTreeNode(parentTreeDict, PDStructureElement.class)); + root.setParentTreeNextKey(nextParentKey); + } + + /** + * Clause 7.18: every visible annotation needs a structure element so it is reachable from the + * tree. Links become Link elements, anything else an Annot. + */ + private List tagAnnotations(PDDocument document, PDStructureTreeRoot root) { + List entries = new ArrayList<>(); + PDStructureElement documentElement = firstDocumentElement(root); + if (documentElement == null) { + return entries; + } + for (int pageIndex = 0; pageIndex < document.getNumberOfPages(); pageIndex++) { + PDPage page = document.getPage(pageIndex); + List annotations; + try { + annotations = page.getAnnotations(); + } catch (IOException e) { + log.debug("Could not read annotations on page {}: {}", pageIndex, e.getMessage()); + continue; + } + for (PDAnnotation annotation : annotations) { + if (annotation == null + || annotation.isHidden() + || annotation.isNoView() + || "Popup".equals(annotation.getSubtype())) { + continue; + } + PDStructureElement element = + new PDStructureElement(annotationType(annotation), documentElement); + element.setPage(page); + + PDObjectReference reference = new PDObjectReference(); + reference.setReferencedObject(annotation); + element.appendKid(reference); + documentElement.appendKid(element); + + int key = nextParentKey++; + annotation.getCOSObject().setInt(STRUCT_PARENT, key); + entries.add(COSInteger.get(key)); + entries.add(element.getCOSObject()); + + if (annotation.getContents() == null || annotation.getContents().isBlank()) { + annotation.setContents(defaultContents(annotation)); + } + } + } + return entries; + } + + /** Clause 7.18.4: widgets need a Form element, links a Link element, everything else Annot. */ + private static String annotationType(PDAnnotation annotation) { + if (annotation instanceof PDAnnotationWidget) { + return StructType.FORM.tag(); + } + if (annotation instanceof PDAnnotationLink) { + return StructType.LINK.tag(); + } + return "Annot"; + } + + private static String defaultContents(PDAnnotation annotation) { + if (annotation instanceof PDAnnotationLink link && link.getAction() != null) { + return "Link"; + } + return annotation.getSubtype() == null ? "Annotation" : annotation.getSubtype(); + } + + private static PDStructureElement firstDocumentElement(PDStructureTreeRoot root) { + for (Object kid : root.getKids()) { + if (kid instanceof PDStructureElement element) { + return element; + } + } + return null; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructType.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructType.java new file mode 100644 index 0000000000..b4634a58c6 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/StructType.java @@ -0,0 +1,62 @@ +package stirling.software.proprietary.pdf.ua; + +/** + * PDF standard structure types emitted by the tagger (ISO 32000-1 14.8.4), limited to the PDF/UA + * subset. {@link #ARTIFACT} is not one: it marks content in the stream and stays out of the tree. + */ +public enum StructType { + DOCUMENT("Document"), + PART("Part"), + SECT("Sect"), + H1("H1"), + H2("H2"), + H3("H3"), + H4("H4"), + H5("H5"), + H6("H6"), + P("P"), + L("L"), + LI("LI"), + LBL("Lbl"), + LBODY("LBody"), + TABLE("Table"), + TR("TR"), + TH("TH"), + TD("TD"), + FIGURE("Figure"), + CAPTION("Caption"), + FORMULA("Formula"), + NOTE("Note"), + FENOTE("FENote"), + LINK("Link"), + /** Wraps a widget annotation; PDF/UA-1 clause 7.18.4 requires widgets to sit inside one. */ + FORM("Form"), + SPAN("Span"), + ARTIFACT("Artifact"); + + private final String tag; + + StructType(String tag) { + this.tag = tag; + } + + /** The name written into the PDF {@code /S} entry. */ + public String tag() { + return tag; + } + + public boolean isHeading() { + return this == H1 || this == H2 || this == H3 || this == H4 || this == H5 || this == H6; + } + + /** Heading level 1-6, or 0 when this is not a heading. */ + public int headingLevel() { + return isHeading() ? ordinal() - H1.ordinal() + 1 : 0; + } + + /** The heading type for a 1-based level, clamped to the H1-H6 range. */ + public static StructType heading(int level) { + int clamped = Math.max(1, Math.min(6, level)); + return values()[H1.ordinal() + clamped - 1]; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggedContentExtractor.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggedContentExtractor.java new file mode 100644 index 0000000000..01c86215d4 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggedContentExtractor.java @@ -0,0 +1,630 @@ +package stirling.software.proprietary.pdf.ua; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.pdfbox.contentstream.operator.Operator; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdfparser.PDFStreamParser; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDFontDescriptor; +import org.apache.pdfbox.pdmodel.font.PDType3Font; +import org.apache.pdfbox.pdmodel.graphics.PDXObject; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.text.PDFTextStripper; +import org.apache.pdfbox.text.TextPosition; +import org.apache.pdfbox.util.Matrix; +import org.apache.pdfbox.util.Vector; + +import lombok.extern.slf4j.Slf4j; + +/** + * Extracts text lines and graphic ops from page streams, tagging each with its operator ordinal. + * Both passes count the same operators in the same order, so ordinals cross-reference. + */ +@Slf4j +public class TaggedContentExtractor { + + /** Glyph size below which a run is treated as noise rather than a line. */ + private static final float MIN_FONT_SIZE = 0.5f; + + public List extract(PDDocument document) throws IOException { + LineCollector collector = new LineCollector(); + collector.setSortByPosition(true); + collector.setStartPage(1); + collector.setEndPage(document.getNumberOfPages()); + collector.writeText(document, Writer.nullWriter()); + + List pages = new ArrayList<>(document.getNumberOfPages()); + for (int i = 0; i < document.getNumberOfPages(); i++) { + PDPage page = document.getPage(i); + List ops = collector.opsFor(i); + List lines = collector.linesFor(i); + boolean dropped = false; + if (ops.size() < maxOrdinal(lines) + 1) { + // Untrusted ordinals: drop the lines so the page is untaggable rather than + // mis-tagged, and flag it so the caller refuses to declare conformance. + log.warn( + "Ordinal mismatch on page {} (ops={}, text={}); skipping page", + i, + ops.size(), + maxOrdinal(lines) + 1); + dropped = !lines.isEmpty(); + lines = List.of(); + } + pages.add( + new PageContent( + i, + lines, + ops, + ops.size(), + collector.preMarkedOn(i), + collector.textSemanticsOn(i), + dropped, + normalisedBox(page))); + } + return pages; + } + + /** + * The page box in the space of extracted line coordinates: origin-zero, width and height + * swapped for 90/270 rotations, because the text engine reports in the rotated frame. + */ + static BBox normalisedBox(PDPage page) { + PDRectangle mediaBox = page.getMediaBox(); + boolean sideways = page.getRotation() % 180 != 0; + float width = sideways ? mediaBox.getHeight() : mediaBox.getWidth(); + float height = sideways ? mediaBox.getWidth() : mediaBox.getHeight(); + return new BBox(0, 0, width, height); + } + + /** Counts images with the token scan alone, skipping the expensive text pass. */ + public int countGraphics(PDDocument document) { + int total = 0; + for (int i = 0; i < document.getNumberOfPages(); i++) { + try { + PDResources resources = document.getPage(i).getResources(); + PDFStreamParser parser = new PDFStreamParser(document.getPage(i)); + List operands = new ArrayList<>(); + Object token; + while ((token = parser.parseNextToken()) != null) { + if (!(token instanceof Operator operator)) { + operands.add((COSBase) token); + continue; + } + if (isGraphicOperator(operator.getName(), operands, resources)) { + total++; + } + operands.clear(); + } + } catch (IOException e) { + log.debug("Could not scan page {} for graphics: {}", i, e.getMessage()); + } + } + return total; + } + + /** True for an inline image, or a Do that resolves to an image XObject. */ + private static boolean isGraphicOperator( + String name, List operands, PDResources resources) { + if ("BI".equals(name)) { + return true; + } + if (!"Do".equals(name) || resources == null || operands.size() != 1) { + return false; + } + if (!(operands.get(0) instanceof COSName resourceName)) { + return false; + } + try { + return resources.getXObject(resourceName) instanceof PDImageXObject; + } catch (IOException e) { + return false; + } + } + + private static int maxOrdinal(List lines) { + return lines.stream().mapToInt(TextLineInfo::endOrdinal).max().orElse(-1); + } + + static BBox toBBox(PDRectangle rect) { + return new BBox( + rect.getLowerLeftX(), + rect.getLowerLeftY(), + rect.getUpperRightX(), + rect.getUpperRightY()); + } + + // --- Operator classification ------------------------------------------- + + private static boolean isPathConstruction(String name) { + return switch (name) { + case "m", "l", "c", "v", "y", "re" -> true; + default -> false; + }; + } + + /** True when a sequence carries replacement or alternative text, which a rebuild would drop. */ + private static boolean carriesTextSemantics(List operands) { + for (COSBase operand : operands) { + if (operand instanceof COSDictionary dictionary + && (dictionary.containsKey(COSName.getPDFName("ActualText")) + || dictionary.containsKey(COSName.getPDFName("Alt")) + || dictionary.containsKey(COSName.E))) { + return true; + } + } + return false; + } + + /** + * Describes one markable operator, placed with the engine's own matrix rather than a + * hand-rolled q/Q/cm stack that would get nesting and form matrices wrong. + */ + private static MarkableOp classify( + String name, + List operands, + PDResources resources, + Matrix ctm, + BBox pathBox, + int ordinal) { + + if ("BI".equals(name)) { + return new MarkableOp(ordinal, MarkableOp.Kind.INLINE_IMAGE, unitSquare(ctm), null); + } + if (MarkableOp.isPathPainting(name)) { + return new MarkableOp(ordinal, MarkableOp.Kind.VECTOR, pathBox, null); + } + if (!"Do".equals(name)) { + return new MarkableOp(ordinal, MarkableOp.Kind.TEXT, BBox.EMPTY, null); + } + COSName resourceName = + operands.size() == 1 && operands.get(0) instanceof COSName n ? n : null; + if (resourceName == null || resources == null) { + return new MarkableOp(ordinal, MarkableOp.Kind.FORM, unitSquare(ctm), null); + } + try { + PDXObject xobject = resources.getXObject(resourceName); + if (xobject instanceof PDImageXObject) { + return new MarkableOp( + ordinal, MarkableOp.Kind.IMAGE, unitSquare(ctm), resourceName.getName()); + } + if (xobject instanceof PDFormXObject form) { + return new MarkableOp( + ordinal, MarkableOp.Kind.FORM, formBox(form, ctm), resourceName.getName()); + } + } catch (IOException e) { + log.debug("Could not resolve XObject {}: {}", resourceName.getName(), e.getMessage()); + } + return new MarkableOp( + ordinal, MarkableOp.Kind.FORM, unitSquare(ctm), resourceName.getName()); + } + + /** + * Extends the running path box with one path-construction operator's points; without it every + * vector had an empty box and charts and vector logos vanished from the structure tree. + */ + private static BBox extendPath(BBox current, String name, List operands, Matrix ctm) { + int pairs = + switch (name) { + case "m", "l" -> 1; + case "re" -> 2; + case "v", "y" -> 2; + case "c" -> 3; + default -> 0; + }; + if (pairs == 0 || operands.size() < pairs * 2) { + return current; + } + + // Deliberately allocation-free; the obvious version cost a third of the extraction budget. + float minX = current.isEmpty() ? Float.MAX_VALUE : current.x0(); + float minY = current.isEmpty() ? Float.MAX_VALUE : current.y0(); + float maxX = current.isEmpty() ? -Float.MAX_VALUE : current.x1(); + float maxY = current.isEmpty() ? -Float.MAX_VALUE : current.y1(); + + for (int pair = 0; pair < pairs; pair++) { + Float x = numberAt(operands, pair * 2); + Float y = numberAt(operands, pair * 2 + 1); + if (x == null || y == null) { + continue; + } + float px = x; + float py = y; + // "re" gives origin plus size, so the second pair is a corner offset from the first. + if ("re".equals(name) && pair == 1) { + Float ox = numberAt(operands, 0); + Float oy = numberAt(operands, 1); + if (ox == null || oy == null) { + continue; + } + px = ox + x; + py = oy + y; + } + float tx = ctm.getScaleX() * px + ctm.getShearX() * py + ctm.getTranslateX(); + float ty = ctm.getShearY() * px + ctm.getScaleY() * py + ctm.getTranslateY(); + minX = Math.min(minX, tx); + minY = Math.min(minY, ty); + maxX = Math.max(maxX, tx); + maxY = Math.max(maxY, ty); + } + return maxX < minX ? current : new BBox(minX, minY, maxX, maxY); + } + + private static Float numberAt(List operands, int index) { + return index < operands.size() + && operands.get(index) instanceof org.apache.pdfbox.cos.COSNumber number + ? number.floatValue() + : null; + } + + /** The unit square mapped through the CTM, which is how images are placed. */ + private static BBox unitSquare(Matrix ctm) { + return transformBox(new BBox(0, 0, 1, 1), ctm); + } + + private static BBox formBox(PDFormXObject form, Matrix ctm) { + PDRectangle box = form.getBBox(); + if (box == null) { + return unitSquare(ctm); + } + Matrix combined = form.getMatrix() != null ? form.getMatrix().multiply(ctm) : ctm; + return transformBox(toBBox(box), combined); + } + + private static BBox transformBox(BBox box, Matrix m) { + float[] xs = new float[4]; + float[] ys = new float[4]; + float[][] corners = { + {box.x0(), box.y0()}, {box.x1(), box.y0()}, + {box.x0(), box.y1()}, {box.x1(), box.y1()} + }; + for (int i = 0; i < 4; i++) { + Vector v = m.transform(new Vector(corners[i][0], corners[i][1])); + xs[i] = v.getX(); + ys[i] = v.getY(); + } + float minX = Math.min(Math.min(xs[0], xs[1]), Math.min(xs[2], xs[3])); + float maxX = Math.max(Math.max(xs[0], xs[1]), Math.max(xs[2], xs[3])); + float minY = Math.min(Math.min(ys[0], ys[1]), Math.min(ys[2], ys[3])); + float maxY = Math.max(Math.max(ys[0], ys[1]), Math.max(ys[2], ys[3])); + return new BBox(minX, minY, maxX, maxY); + } + + // --- Text pass --------------------------------------------------------- + + /** Marker recorded for each glyph so a finished line knows where it came from. */ + private record GlyphOrigin(int ordinal, boolean marked) {} + + private static final class LineCollector extends PDFTextStripper { + + private final Map> byPage = new HashMap<>(); + private final Map> opsByPage = new HashMap<>(); + private final Map preMarkedByPage = new HashMap<>(); + private final Map textSemanticsByPage = new HashMap<>(); + private final Map origins = new IdentityHashMap<>(); + private final List lineBuffer = new ArrayList<>(); + private final List lineWords = new ArrayList<>(); + private final StringBuilder lineText = new StringBuilder(); + + private int ordinal = -1; + private int markedDepth; + private int nestedDepth; + private BBox pathBox = BBox.EMPTY; + private int syntheticDepth; + private float pageHeight; + private int pageIndex; + + LineCollector() throws IOException { + super(); + } + + List linesFor(int index) { + return byPage.getOrDefault(index, List.of()); + } + + List opsFor(int index) { + return opsByPage.getOrDefault(index, List.of()); + } + + boolean preMarkedOn(int index) { + return preMarkedByPage.getOrDefault(index, false); + } + + boolean textSemanticsOn(int index) { + return textSemanticsByPage.getOrDefault(index, false); + } + + @Override + protected void startPage(PDPage page) throws IOException { + ordinal = -1; + markedDepth = 0; + nestedDepth = 0; + syntheticDepth = 0; + pathBox = BBox.EMPTY; + origins.clear(); + lineBuffer.clear(); + lineWords.clear(); + lineText.setLength(0); + // Dir-adjusted glyph coordinates live in the rotated frame, so the flip must too. + pageHeight = normalisedBox(page).height(); + pageIndex = getCurrentPageNo() - 1; + super.startPage(page); + } + + @Override + protected void endPage(PDPage page) throws IOException { + flushLine(); + super.endPage(page); + } + + /** + * Counts only operators physically present in the page's own stream: PDFBox re-enters here + * with synthetic calls for {@code '} and {@code "}, and descends into form XObjects. + */ + @Override + protected void processOperator(Operator operator, List operands) + throws IOException { + String name = operator.getName(); + if (nestedDepth == 0 && syntheticDepth == 0) { + if (isPathConstruction(name)) { + pathBox = + extendPath( + pathBox, + name, + operands, + getGraphicsState().getCurrentTransformationMatrix()); + } + if (MarkableOp.isMarkableOperator(name)) { + ordinal++; + // Classified here rather than in a second parse of the same stream: the engine + // already has the operands and the live transformation matrix. + opsByPage + .computeIfAbsent(pageIndex, k -> new ArrayList<>()) + .add( + classify( + name, + operands, + getResources(), + getGraphicsState().getCurrentTransformationMatrix(), + pathBox, + ordinal)); + if (MarkableOp.isPathPainting(name)) { + pathBox = BBox.EMPTY; + } + } else if ("n".equals(name)) { + pathBox = BBox.EMPTY; + } else if ("BDC".equals(name) || "BMC".equals(name)) { + markedDepth++; + preMarkedByPage.put(pageIndex, true); + if (carriesTextSemantics(operands)) { + textSemanticsByPage.put(pageIndex, true); + } + } else if ("EMC".equals(name) && markedDepth > 0) { + markedDepth--; + } + } + boolean synthesises = "'".equals(name) || "\"".equals(name); + if (synthesises) { + syntheticDepth++; + } + try { + super.processOperator(operator, operands); + } finally { + if (synthesises) { + syntheticDepth--; + } + } + } + + @Override + public void showForm(PDFormXObject form) throws IOException { + nestedDepth++; + try { + super.showForm(form); + } finally { + nestedDepth--; + } + } + + @Override + public void showTransparencyGroup(PDTransparencyGroup group) throws IOException { + nestedDepth++; + try { + super.showTransparencyGroup(group); + } finally { + nestedDepth--; + } + } + + @Override + protected void showType3Glyph( + Matrix textRenderingMatrix, + PDType3Font font, + int code, + org.apache.pdfbox.util.Vector displacement) + throws IOException { + nestedDepth++; + try { + super.showType3Glyph(textRenderingMatrix, font, code, displacement); + } finally { + nestedDepth--; + } + } + + @Override + protected void processChildStream( + org.apache.pdfbox.contentstream.PDContentStream contentStream, PDPage page) + throws IOException { + nestedDepth++; + try { + super.processChildStream(contentStream, page); + } finally { + nestedDepth--; + } + } + + @Override + protected void processTextPosition(TextPosition text) { + origins.put(text, new GlyphOrigin(ordinal, markedDepth > 0)); + super.processTextPosition(text); + } + + @Override + protected void writeString(String text, List positions) { + lineText.append(text); + lineBuffer.addAll(positions); + WordInfo word = buildWord(text, positions); + if (word != null) { + lineWords.add(word); + } + } + + private WordInfo buildWord(String text, List positions) { + if (text == null || text.isBlank() || positions.isEmpty()) { + return null; + } + Bounds bounds = new Bounds(); + for (TextPosition tp : positions) { + bounds.accept(tp, pageHeight, origins.get(tp)); + } + if (bounds.end < 0) { + return null; + } + return new WordInfo( + text, + bounds.box(), + bounds.start, + bounds.end, + bounds.dominantSize(), + bounds.bold); + } + + @Override + protected void writeWordSeparator() { + lineText.append(' '); + } + + @Override + protected void writeLineSeparator() { + flushLine(); + } + + @Override + protected void writeParagraphSeparator() { + flushLine(); + } + + private void flushLine() { + if (lineBuffer.isEmpty()) { + lineText.setLength(0); + lineWords.clear(); + return; + } + TextLineInfo line = buildLine(); + lineBuffer.clear(); + lineWords.clear(); + lineText.setLength(0); + if (line != null) { + byPage.computeIfAbsent(pageIndex, k -> new ArrayList<>()).add(line); + } + } + + private TextLineInfo buildLine() { + String text = lineText.toString(); + if (text.isBlank()) { + return null; + } + Bounds bounds = new Bounds(); + for (TextPosition tp : lineBuffer) { + bounds.accept(tp, pageHeight, origins.get(tp)); + } + if (bounds.end < 0) { + return null; + } + return new TextLineInfo( + pageIndex, + text, + bounds.box(), + bounds.dominantSize(), + bounds.bold, + bounds.start, + bounds.end, + bounds.marked, + List.copyOf(lineWords)); + } + } + + /** Accumulates glyph geometry, ordinals and font signals for a word or a line. */ + private static final class Bounds { + private float minX = Float.MAX_VALUE; + private float maxX = -Float.MAX_VALUE; + private float minY = Float.MAX_VALUE; + private float maxY = -Float.MAX_VALUE; + private int start = Integer.MAX_VALUE; + private int end = -1; + private boolean marked; + private boolean bold; + private final Map sizeCounts = new HashMap<>(); + + void accept(TextPosition tp, float pageHeight, GlyphOrigin origin) { + float top = pageHeight - tp.getYDirAdj(); + float bottom = top - Math.max(tp.getHeightDir(), 0); + minX = Math.min(minX, tp.getXDirAdj()); + maxX = Math.max(maxX, tp.getXDirAdj() + tp.getWidthDirAdj()); + minY = Math.min(minY, bottom); + maxY = Math.max(maxY, top); + + if (origin != null) { + start = Math.min(start, origin.ordinal()); + end = Math.max(end, origin.ordinal()); + marked |= origin.marked(); + } + float size = tp.getFontSizeInPt(); + if (size > MIN_FONT_SIZE) { + sizeCounts.merge(round(size), 1, Integer::sum); + } + bold |= isBold(tp); + } + + BBox box() { + return new BBox(minX, minY, maxX, maxY); + } + + float dominantSize() { + return sizeCounts.entrySet().stream() + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElse(0f); + } + + private static float round(float value) { + return Math.round(value * 10f) / 10f; + } + + private static boolean isBold(TextPosition tp) { + if (tp.getFont() == null) { + return false; + } + String name = tp.getFont().getName(); + if (name != null && name.toLowerCase().contains("bold")) { + return true; + } + PDFontDescriptor descriptor = tp.getFont().getFontDescriptor(); + return descriptor != null + && (descriptor.getFontWeight() >= 600 || descriptor.isForceBold()); + } + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggingOptions.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggingOptions.java new file mode 100644 index 0000000000..bba15ed09f --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggingOptions.java @@ -0,0 +1,63 @@ +package stirling.software.proprietary.pdf.ua; + +import java.util.Map; + +import lombok.Builder; +import lombok.Getter; + +/** Inputs that change how a document is tagged. */ +@Getter +@Builder(toBuilder = true) +public class TaggingOptions { + + /** What to do when the source already has a structure tree. */ + public enum ExistingTags { + /** Leave the tree alone and fix only document-level requirements. */ + KEEP, + /** Discard the tree and derive a new one. */ + REBUILD, + /** Keep a usable tree, rebuild an empty or trivially broken one. */ + AUTO + } + + /** How images with no alternative description are handled. */ + public enum FigurePolicy { + /** Leave undescribed so validation fails honestly; a faked {@code /Alt} helps nobody. */ + REQUIRE_ALT, + /** Treat every image as decoration and mark it as an artifact. */ + MARK_DECORATIVE + } + + @Builder.Default private PdfUaProfile profile = PdfUaProfile.UA1; + + /** BCP-47 language tag for the document, for example {@code en-GB}. */ + private String language; + + /** Replace a language the document already declares. Off, so a French file stays French. */ + @Builder.Default private boolean overrideLanguage = false; + + private String title; + + /** Last resort when no title is given and none can be derived; pass the uploaded filename. */ + private String fallbackTitle; + + /** Embed any font the document references but does not carry, which clause 7.21 requires. */ + @Builder.Default private boolean embedFonts = true; + + /** Leave the PDF version alone; raising it would break PDF/A-1, defined on PDF 1.4. */ + @Builder.Default private boolean preservePdfVersion = false; + + @Builder.Default private ExistingTags existingTags = ExistingTags.AUTO; + + @Builder.Default private FigurePolicy figurePolicy = FigurePolicy.REQUIRE_ALT; + + /** Alternative descriptions supplied by the caller, keyed by "pageIndex:ordinal". */ + @Builder.Default private Map altTextByFigure = Map.of(); + + /** What the document said before font embedding rewrote it; see {@link SourceFacts}. */ + @Builder.Default private SourceFacts sourceFacts = SourceFacts.NONE; + + public String altTextFor(int pageIndex, int ordinal) { + return altTextByFigure.get(pageIndex + ":" + ordinal); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggingResult.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggingResult.java new file mode 100644 index 0000000000..d54fad5de8 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TaggingResult.java @@ -0,0 +1,42 @@ +package stirling.software.proprietary.pdf.ua; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; + +/** What a tagging run produced, for the conversion report. */ +@Getter +public class TaggingResult { + + private final List warnings = new ArrayList<>(); + private final DocumentStructure structure; + private final boolean rebuilt; + private final int taggedElements; + private final int artifacts; + private final int figuresNeedingAlt; + + /** True when text was hidden as artifacts; the caller must not declare conformance. */ + private final boolean contentSuppressed; + + public TaggingResult(DocumentStructure structure, boolean rebuilt) { + this.structure = structure; + this.rebuilt = rebuilt; + this.warnings.addAll(structure.getWarnings()); + int[] elements = {0}; + structure.visit( + block -> { + if (!block.isArtifact()) { + elements[0]++; + } + }); + this.taggedElements = elements[0]; + this.artifacts = structure.artifactCount(); + this.figuresNeedingAlt = structure.figuresWithoutAlt().size(); + this.contentSuppressed = structure.isTextSuppressed(); + } + + public boolean needsHumanReview() { + return figuresNeedingAlt > 0 || !warnings.isEmpty(); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TextLineInfo.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TextLineInfo.java new file mode 100644 index 0000000000..67f37d1c8d --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/TextLineInfo.java @@ -0,0 +1,42 @@ +package stirling.software.proprietary.pdf.ua; + +import java.util.List; + +/** + * A run of text on one baseline, with the operator ordinals that produced it. {@code preMarked} + * means the source stream already wrapped this text in BDC/EMC. + */ +public record TextLineInfo( + int pageIndex, + String text, + BBox bbox, + float dominantFontSize, + boolean bold, + int startOrdinal, + int endOrdinal, + boolean preMarked, + List words) { + + public boolean isBlank() { + return text == null || text.isBlank(); + } + + public int charCount() { + return text == null ? 0 : text.strip().length(); + } + + public int wordCount() { + return (int) words.stream().filter(w -> !w.isBlank()).count(); + } + + /** True when every word occupies its own operator run, so cells can be tagged separately. */ + public boolean wordsAreSeparable() { + List real = words.stream().filter(w -> !w.isBlank()).toList(); + for (int i = 1; i < real.size(); i++) { + if (!real.get(i - 1).isSeparableFrom(real.get(i))) { + return false; + } + } + return true; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/WordInfo.java b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/WordInfo.java new file mode 100644 index 0000000000..515775b6f8 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/pdf/ua/WordInfo.java @@ -0,0 +1,18 @@ +package stirling.software.proprietary.pdf.ua; + +/** + * A whitespace-delimited run of glyphs, with the operator ordinals that produced it. Cell detection + * needs both: geometry to find cells, ordinals to tell whether they can be tagged separately. + */ +public record WordInfo( + String text, BBox bbox, int startOrdinal, int endOrdinal, float fontSize, boolean bold) { + + public boolean isBlank() { + return text == null || text.isBlank(); + } + + /** True when this word shares no operator with the other, so both can carry their own MCID. */ + public boolean isSeparableFrom(WordInfo other) { + return endOrdinal < other.startOrdinal || other.endOrdinal < startOrdinal; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/policy/config/AdminPolicyManagementAuthority.java b/app/proprietary/src/main/java/stirling/software/proprietary/policy/config/AdminPolicyManagementAuthority.java index a49c8e5aa9..6d8229aa26 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/policy/config/AdminPolicyManagementAuthority.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/policy/config/AdminPolicyManagementAuthority.java @@ -26,6 +26,11 @@ public class AdminPolicyManagementAuthority implements PolicyManagementAuthority return userService.isCurrentUserAdmin(); } + @Override + public boolean canTriggerPolicies() { + return userService.isCurrentUserAdmin(); + } + @Override public Long currentUserTeamId() { String username = userService.getCurrentUsername(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/policy/config/PolicyManagementAuthority.java b/app/proprietary/src/main/java/stirling/software/proprietary/policy/config/PolicyManagementAuthority.java index 0ea3c298ad..d7e4f50ad1 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/policy/config/PolicyManagementAuthority.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/policy/config/PolicyManagementAuthority.java @@ -12,6 +12,17 @@ public interface PolicyManagementAuthority { /** Whether the current user may create, edit, or delete policies (for their own team). */ boolean canEditPolicies(); + /** + * Whether the current user may run a policy against its configured sources (the manual + * "run now" sweep). Kept separate from {@link #canEditPolicies()} because the two are distinct + * capabilities, even where a deployment grants both to the same people: a sweep operates on the + * team's configured sources using the server's stored connection credentials, which makes it a + * policy-management capability rather than ordinary use. Running a policy over the caller's + * own uploaded files is not covered by this and stays open to every team member — that + * is ordinary editor enforcement. + */ + boolean canTriggerPolicies(); + /** * The team that scopes the current user's policies — the team a new policy is stamped with and * the only team whose policies the user may see/run/edit. {@code null} when it can't be diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/policy/controller/PolicyController.java b/app/proprietary/src/main/java/stirling/software/proprietary/policy/controller/PolicyController.java index 506bb75578..9b9fca4133 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/policy/controller/PolicyController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/policy/controller/PolicyController.java @@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.RequestContextHolder; @@ -51,6 +52,7 @@ import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFileManager; import stirling.software.proprietary.audit.AuditContext; import stirling.software.proprietary.policy.asset.PolicyAssetCleaner; +import stirling.software.proprietary.policy.asset.PolicyAssetResolver; import stirling.software.proprietary.policy.config.PolicyAccessGuard; import stirling.software.proprietary.policy.config.PolicyManagementAuthority; import stirling.software.proprietary.policy.engine.PolicyRunHandle; @@ -106,6 +108,7 @@ public class PolicyController { private final PolicyTriggerManager policyTriggerManager; private final PolicyOverviewService policyOverviewService; private final PolicyAssetCleaner assetCleaner; + private final PolicyAssetResolver assetResolver; private final ProcessedLedger processedLedger; private final List policyTriggers; private final ApplicationProperties applicationProperties; @@ -125,12 +128,13 @@ public class PolicyController { + " endpoint and download outputs via /api/v1/general/files/{id}.") public ResponseEntity> run( @RequestPart("json") PipelineDefinition definition, + @RequestParam(value = "policyId", required = false) String policyId, @Valid @ModelAttribute PolicyRunFiles files) throws IOException { stampPolicyAudit(definition); requireRunnable(definition); validateAdHocRun(definition); - PolicyInputs inputs = toInputs(files); + PolicyInputs inputs = resolveStoredAssets(policyId, toInputs(files)); PolicyRunHandle handle = policyRunner.runAdHoc(definition, inputs, PolicyProgressListener.NOOP); recordEditorDocs(inputs); @@ -146,12 +150,13 @@ public class PolicyController { + " 'cancelled', or 'waiting' event carrying the final run view.") public SseEmitter runStream( @RequestPart("json") PipelineDefinition definition, + @RequestParam(value = "policyId", required = false) String policyId, @Valid @ModelAttribute PolicyRunFiles files) throws IOException { stampPolicyAudit(definition); requireRunnable(definition); validateAdHocRun(definition); - PolicyInputs inputs = toInputs(files); + PolicyInputs inputs = resolveStoredAssets(policyId, toInputs(files)); SseEmitter emitter = new SseEmitter(applicationProperties.getPolicies().getStreamTimeoutMs()); @@ -432,21 +437,47 @@ public class PolicyController { * admin gets no say on SaaS. Team scoping (which team's policies) is enforced separately by * {@link PolicyAccessGuard}. Every mutation routes through {@link #savePolicy} (pause/resume * re-save with a flipped {@code enabled} flag) or {@link #deletePolicy}, so gating those two - * covers them all; runs ({@code /run}) stay open to the team. Single-user deployments (login - * disabled) have no such role, so they trust the local operator. The path allowlist for folder - * sources/outputs is enforced separately by {@link PolicyValidator} at validation time. + * covers them all; runs over the caller's own files ({@code /{id}/run}) stay open to the team, + * while source sweeps are gated by {@link #requirePolicySweepAllowed}. Single-user deployments + * (login disabled) have no such role, so they trust the local operator. The path allowlist for + * folder sources/outputs is enforced separately by {@link PolicyValidator} at validation time. */ private void requirePolicyEditingAllowed() { - if (!applicationProperties.getSecurity().isEnableLogin()) { - return; - } - if (!policyManagementAuthority.canEditPolicies()) { + if (!policyEditingAllowed()) { throw new ResponseStatusException( HttpStatus.FORBIDDEN, "Policies may only be created or modified by a team leader"); } } + /** + * Sweeping a policy's configured sources requires the same role as managing policies: the sweep + * operates on the team's configured sources using the server's stored connection credentials, + * which makes it a policy-management capability rather than ordinary use, and team scoping on + * its own does not express that. Deliberately narrower than it looks: it gates only the sweep, + * not {@link #runStoredPolicy}, because running a policy over documents the caller supplied is + * ordinary editor enforcement that every member performs on upload and export. + */ + private void requirePolicySweepAllowed() { + if (!applicationProperties.getSecurity().isEnableLogin()) { + return; + } + if (!policyManagementAuthority.canTriggerPolicies()) { + throw new ResponseStatusException( + HttpStatus.FORBIDDEN, + "Not permitted to run this policy against its configured sources"); + } + } + + /** + * Whether the caller may create/modify policies (a team leader, or any operator when login is + * off). + */ + private boolean policyEditingAllowed() { + return !applicationProperties.getSecurity().isEnableLogin() + || policyManagementAuthority.canEditPolicies(); + } + @GetMapping @Operation( summary = "List policies", @@ -571,8 +602,10 @@ public class PolicyController { + " the enabled flag (which only gates automatic triggering). Returns" + " the ids of the runs started (poll the run-status endpoint for each)" + " plus what the sweep skipped - already-processed, parked-by-failure," - + " and in-flight counts - so an empty result explains itself.") + + " and in-flight counts - so an empty result explains itself. Requires" + + " the policy-management role.") public ResponseEntity trigger(@PathVariable String policyId) { + requirePolicySweepAllowed(); Policy policy = policyStore .get(policyId) @@ -650,6 +683,25 @@ public class PolicyController { inputs.primary().size()); } + /** + * Resolve a test run's stored {@code asset:} bindings from the saved policy the builder is + * editing, so their bytes need not be re-uploaded. Scoped to that policy (the resolver loads + * only the assets it references, in its own team) and gated to policy editors - the same + * authority that can read asset bytes - so a member can't rebind a policy's stored asset into + * an ad-hoc step to read it back. A blank id (an unsaved pipeline has no stored bindings) or an + * inaccessible policy leaves the run-supplied inputs untouched. + */ + private PolicyInputs resolveStoredAssets(String policyId, PolicyInputs inputs) { + if (policyId == null || policyId.isBlank() || !policyEditingAllowed()) { + return inputs; + } + return policyStore + .get(policyId) + .filter(policyAccessGuard::canAccess) + .map(policy -> assetResolver.resolve(policy, inputs)) + .orElse(inputs); + } + /** * Turn the typed run files into engine {@link PolicyInputs}: the primary documents plus the * named supporting-file store, where each asset's {@code key} is the name a step references diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/AccessibilityAuditService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/AccessibilityAuditService.java new file mode 100644 index 0000000000..869d489a04 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/AccessibilityAuditService.java @@ -0,0 +1,187 @@ +package stirling.software.proprietary.service.ua; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import stirling.software.common.util.ExceptionUtils; +import stirling.software.proprietary.model.api.ua.AccessibilityIssue; +import stirling.software.proprietary.model.api.ua.AccessibilityReport; +import stirling.software.proprietary.model.api.ua.FigureDescriptor; +import stirling.software.proprietary.model.api.ua.UaValidationResult; +import stirling.software.proprietary.pdf.ua.BBox; +import stirling.software.proprietary.pdf.ua.DocumentStructure; +import stirling.software.proprietary.pdf.ua.LayoutAnalyzer; +import stirling.software.proprietary.pdf.ua.PageContent; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.StructBlock; +import stirling.software.proprietary.pdf.ua.StructType; +import stirling.software.proprietary.pdf.ua.TaggedContentExtractor; + +/** Produces an accessibility report without changing the document. */ +@Service +@Slf4j +@RequiredArgsConstructor +public class AccessibilityAuditService { + + /** Checks no validator can make; omitting them implies the work does not exist. */ + private static final List HUMAN_CHECKS = + List.of( + "Is the reading order correct for someone who cannot see the layout?", + "Does each alternative description convey what the image is for, not just what" + + " it looks like?", + "Are headings used for structure rather than for visual emphasis?", + "Is any information conveyed by colour alone also available another way?", + "Do tables have headers that identify the right rows and columns?", + "Is the document language correct, including for quoted passages?", + "Do links describe their destination rather than saying 'click here'?"); + + /** The report walks every page and validates, so it carries the conversion's own caps. */ + private static final long MAX_INPUT_BYTES = 100L * 1024 * 1024; + + private static final int MAX_PAGES = 2000; + + private final PdfUaValidationService validationService; + + public AccessibilityReport audit(byte[] pdfBytes, PdfUaProfile profile) throws IOException { + enforceLimits(pdfBytes); + AccessibilityReport report = new AccessibilityReport(); + report.setProfile(profile.displayName()); + + UaValidationResult validation = validationService.validate(pdfBytes, profile); + report.setIssues(validation.issues()); + report.setPassesAutomatedChecks(validation.compliant()); + report.setHumanChecks(HUMAN_CHECKS); + + int fixable = 0; + int needsInput = 0; + for (AccessibilityIssue issue : validation.issues()) { + if (issue.isAutoFixable()) { + fixable++; + } else { + needsInput++; + } + } + report.setAutomaticallyFixable(fixable); + report.setNeedsInput(needsInput); + + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + populateSummary(document, report); + report.setFiguresNeedingDescription(figuresNeedingDescription(document)); + } catch (IOException e) { + log.debug("Could not inspect document for the summary: {}", e.getMessage()); + } + return report; + } + + /** + * Rejects before the expensive pass. An unreadable file is left to the report itself to say. + */ + private static void enforceLimits(byte[] pdfBytes) { + if (pdfBytes.length > MAX_INPUT_BYTES) { + throw ExceptionUtils.createIllegalArgumentException( + "error.fileTooLarge", + "This PDF is {0} MB. The accessibility report is limited to {1} MB.", + pdfBytes.length / (1024 * 1024), + MAX_INPUT_BYTES / (1024 * 1024)); + } + int pages; + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + pages = document.getNumberOfPages(); + } catch (IOException e) { + return; + } + if (pages > MAX_PAGES) { + throw ExceptionUtils.createIllegalArgumentException( + "error.tooManyPages", + "This PDF has {0} pages. The accessibility report is limited to {1} pages;" + + " split it first.", + pages, + MAX_PAGES); + } + } + + private void populateSummary(PDDocument document, AccessibilityReport report) + throws IOException { + PDDocumentCatalog catalog = document.getDocumentCatalog(); + AccessibilityReport.Summary summary = report.getSummary(); + + report.setTagged(catalog.getStructureTreeRoot() != null); + report.setDeclaresConformance(declaresUa(document)); + + summary.setPages(document.getNumberOfPages()); + summary.setEncrypted(document.isEncrypted()); + summary.setHasLanguage(catalog.getLanguage() != null && !catalog.getLanguage().isBlank()); + + String title = document.getDocumentInformation().getTitle(); + summary.setHasTitle(title != null && !title.isBlank()); + + PDViewerPreferences preferences = catalog.getViewerPreferences(); + summary.setDisplaysDocTitle(preferences != null && preferences.displayDocTitle()); + + Set unembedded = FontEmbeddingService.findUnembeddedFonts(document); + summary.setUnembeddedFonts(unembedded.size()); + summary.setAllFontsEmbedded(unembedded.isEmpty()); + + try { + summary.setFigures(new TaggedContentExtractor().countGraphics(document)); + } catch (Exception e) { + log.debug("Could not count figures: {}", e.getMessage()); + } + } + + /** + * Lists the figures a conversion would leave undescribed, running the converter's own analysis + * because counting raster images would miss vector charts and existing descriptions. + */ + private List figuresNeedingDescription(PDDocument document) { + try { + List pages = new TaggedContentExtractor().extract(document); + DocumentStructure structure = new LayoutAnalyzer().analyse(pages); + List figures = new ArrayList<>(); + for (StructBlock block : structure.figuresWithoutAlt()) { + int ordinal = block.getRanges().isEmpty() ? -1 : block.getRanges().get(0).start(); + BBox box = block.getBbox(); + figures.add( + new FigureDescriptor( + block.getPageIndex() + ":" + ordinal, + block.getPageIndex() + 1, + block.getType() == StructType.FORMULA ? "formula" : "figure", + box.x0(), + box.y0(), + box.width(), + box.height(), + block.getAlt())); + } + return figures; + } catch (Exception e) { + log.debug("Could not enumerate figures: {}", e.getMessage()); + return List.of(); + } + } + + /** True when the XMP packet carries a pdfuaid identifier. */ + private static boolean declaresUa(PDDocument document) { + try { + var metadata = document.getDocumentCatalog().getMetadata(); + if (metadata == null) { + return false; + } + String xmp = + new String(metadata.toByteArray(), java.nio.charset.StandardCharsets.UTF_8); + return xmp.contains("pdfuaid"); + } catch (IOException e) { + return false; + } + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/FontEmbeddingService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/FontEmbeddingService.java new file mode 100644 index 0000000000..9c38b54f6b --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/FontEmbeddingService.java @@ -0,0 +1,254 @@ +package stirling.software.proprietary.service.ua; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.springframework.stereotype.Service; + +import lombok.extern.slf4j.Slf4j; + +import stirling.software.common.util.ProcessExecutor; +import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; + +/** + * Embeds any font the document references but does not carry, as PDF/UA-1 clause 7.21 requires. + * Ghostscript does the embedding and discards the structure tree, so this must run before tagging. + */ +@Service +@Slf4j +public class FontEmbeddingService { + + public boolean hasUnembeddedFonts(byte[] pdfBytes) { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return !findUnembeddedFonts(document).isEmpty(); + } catch (IOException e) { + log.debug("Could not inspect fonts: {}", e.getMessage()); + return false; + } + } + + public static Set findUnembeddedFonts(PDDocument document) { + Set missing = new HashSet<>(); + for (PDPage page : document.getPages()) { + PDResources resources = page.getResources(); + if (resources == null) { + continue; + } + for (COSName name : resources.getFontNames()) { + try { + PDFont font = resources.getFont(name); + if (font != null && !font.isEmbedded()) { + missing.add(font.getName()); + } + } catch (IOException e) { + log.debug("Could not read font {}: {}", name.getName(), e.getMessage()); + } + } + } + return missing; + } + + /** + * Returns the document with all fonts embedded, or the input unchanged. Never throws: failing + * to embed is a reportable shortfall, not a reason to abandon the conversion. + */ + public Result embedFonts(byte[] pdfBytes) { + Set missing; + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + missing = findUnembeddedFonts(document); + } catch (IOException e) { + return new Result( + pdfBytes, false, Set.of(), "Could not inspect fonts: " + e.getMessage()); + } + if (missing.isEmpty()) { + return new Result(pdfBytes, false, Set.of(), null); + } + if (!isGhostscriptAvailable()) { + return new Result( + pdfBytes, + false, + missing, + "Ghostscript is not installed, so " + + missing.size() + + " unembedded font(s) could not be embedded. PDF/UA requires every font" + + " to be embedded."); + } + + Path workingDir = null; + try { + workingDir = Files.createTempDirectory("pdfua_fonts_"); + Path input = workingDir.resolve("input.pdf"); + Path output = workingDir.resolve("output.pdf"); + Files.write(input, pdfBytes); + + ProcessExecutorResult result = + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(command(input, output, workingDir)); + + if (result.getRc() != 0 || !Files.exists(output)) { + return new Result( + pdfBytes, + false, + missing, + "Font embedding failed with code " + result.getRc()); + } + byte[] embedded = Files.readAllBytes(output); + + // Ghostscript can exit 0 having written a blank page, so keep the original rather than + // return an empty document. + if (!survived(pdfBytes, embedded)) { + log.warn("Ghostscript produced a degenerate document; keeping the original"); + return new Result( + pdfBytes, + false, + missing, + "Font embedding was skipped because the embedder returned a document that" + + " had lost content. " + + missing.size() + + " font(s) remain unembedded."); + } + + // It can also exit 0 while simply leaving fonts unembedded. + Set remaining; + try (PDDocument check = Loader.loadPDF(embedded)) { + remaining = findUnembeddedFonts(check); + } + if (!remaining.isEmpty()) { + return new Result( + embedded, + true, + remaining, + remaining.size() + + " font(s) could not be embedded (" + + String.join(", ", remaining) + + "). PDF/UA requires every font to be embedded."); + } + log.info("Embedded {} previously unembedded font(s)", missing.size()); + return new Result(embedded, true, missing, null); + + } catch (Exception e) { + log.warn("Font embedding failed: {}", e.getMessage()); + return new Result(pdfBytes, false, missing, "Font embedding failed: " + e.getMessage()); + } finally { + deleteQuietly(workingDir); + } + } + + /** + * True when the rewritten document still holds the original's content. A collapse in page count + * or content-stream size is the only signature of a failed rewrite the exit code hides. + */ + private static boolean survived(byte[] original, byte[] rewritten) { + try (PDDocument before = Loader.loadPDF(original); + PDDocument after = Loader.loadPDF(rewritten)) { + if (after.getNumberOfPages() != before.getNumberOfPages()) { + return false; + } + long beforeBytes = contentBytes(before); + long afterBytes = contentBytes(after); + if (beforeBytes == 0) { + return true; + } + return afterBytes * 20L >= beforeBytes; + } catch (IOException e) { + log.debug("Could not compare documents after embedding: {}", e.getMessage()); + return false; + } + } + + private static long contentBytes(PDDocument document) { + long total = 0; + for (PDPage page : document.getPages()) { + try (InputStream in = page.getContents()) { + if (in != null) { + byte[] buffer = new byte[8192]; + int read; + while ((read = in.read(buffer)) > 0) { + total += read; + } + } + } catch (IOException e) { + log.debug("Could not measure page content: {}", e.getMessage()); + } + } + return total; + } + + private static List command(Path input, Path output, Path workingDir) { + List command = new ArrayList<>(); + command.add("gs"); + command.add("--permit-file-read=" + workingDir.toAbsolutePath()); + command.add("--permit-file-write=" + workingDir.toAbsolutePath()); + command.add("-sDEVICE=pdfwrite"); + command.add("-dEmbedAllFonts=true"); + command.add("-dSubsetFonts=true"); + command.add("-dCompressFonts=true"); + command.add("-dNOSUBSTFONTS=false"); + command.add("-dPDFSETTINGS=/prepress"); + command.add("-dNOPAUSE"); + command.add("-dBATCH"); + command.add("-sOutputFile=" + output.toAbsolutePath()); + command.add(input.toAbsolutePath().toString()); + return command; + } + + /** Cached after the first probe: availability does not change mid-process. */ + private volatile Boolean ghostscriptAvailable; + + private boolean isGhostscriptAvailable() { + Boolean cached = ghostscriptAvailable; + if (cached != null) { + return cached; + } + boolean available; + try { + ProcessExecutorResult result = + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(List.of("gs", "--version")); + available = result.getRc() == 0; + } catch (Exception e) { + log.debug("Ghostscript availability check failed: {}", e.getMessage()); + available = false; + } + ghostscriptAvailable = available; + return available; + } + + private static void deleteQuietly(Path directory) { + if (directory == null) { + return; + } + try (Stream stream = Files.walk(directory)) { + stream.sorted(Comparator.reverseOrder()) + .forEach( + path -> { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + log.debug("Could not delete {}", path); + } + }); + } catch (IOException e) { + log.debug("Could not clean {}", directory); + } + } + + /** + * @param warning non-null when fonts remain unembedded, for the conversion report + */ + public record Result(byte[] pdfBytes, boolean changed, Set fonts, String warning) {} +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfUaConversionService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfUaConversionService.java new file mode 100644 index 0000000000..1d0920c109 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfUaConversionService.java @@ -0,0 +1,251 @@ +package stirling.software.proprietary.service.ua; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.model.api.ua.UaValidationResult; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.PdfUaTagger; +import stirling.software.proprietary.pdf.ua.SourceFacts; +import stirling.software.proprietary.pdf.ua.TaggingOptions; +import stirling.software.proprietary.pdf.ua.TaggingResult; + +/** + * Converts a PDF to PDF/UA. The declaration is written first and withdrawn unless validation + * passes, so a returned file either conforms or does not claim to. + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class PdfUaConversionService { + + private final PdfUaValidationService validationService; + private final FontEmbeddingService fontEmbeddingService; + private final stirling.software.common.service.CustomPDFDocumentFactory pdfDocumentFactory; + + /** Matches the cap GetInfoOnPDF already applies to comparable whole-document work. */ + private static final long MAX_INPUT_BYTES = 100L * 1024 * 1024; + + /** Beyond this the structure model alone runs to hundreds of megabytes. */ + private static final int MAX_PAGES = 2000; + + public PdfUaConversionOutcome convert(byte[] input, TaggingOptions options) throws IOException { + if (input.length > MAX_INPUT_BYTES) { + throw new IOException( + "This PDF is " + + (input.length / (1024 * 1024)) + + " MB. PDF/UA conversion is limited to " + + (MAX_INPUT_BYTES / (1024 * 1024)) + + " MB."); + } + PdfUaProfile profile = options.getProfile(); + List warnings = new ArrayList<>(); + + // Read the document's own facts before anything rewrites it. Font embedding runs + // Ghostscript over the whole file, which discards the structure tree, /Lang and XFA, so + // every guard and every "what did the source say" question must be answered from here. + SourceFacts facts; + try (PDDocument original = load(input)) { + rejectUnsupportedSource(original); + warnSignatures(original, warnings); + facts = SourceFacts.of(original); + } + + byte[] source = input; + if (options.isEmbedFonts()) { + // Must precede tagging: the embedder rewrites the file and drops any structure tree. + FontEmbeddingService.Result fonts = fontEmbeddingService.embedFonts(input); + source = fonts.pdfBytes(); + if (fonts.warning() != null) { + warnings.add(fonts.warning()); + } + source = keepTagsOverFonts(input, source, facts, options, warnings); + } + + TaggingOptions effective = options.toBuilder().sourceFacts(facts).build(); + + // Tag and declare in one pass; the claim is withdrawn below if validation disagrees. + byte[] declared; + TaggingResult taggingResult; + PdfUaTagger tagger = new PdfUaTagger(); + try (PDDocument document = load(source)) { + rejectEncrypted(document); + taggingResult = tagger.tag(document, effective); + warnings.addAll(taggingResult.getWarnings()); + tagger.declareConformance(document, profile); + declared = save(document); + } + + UaValidationResult validation = validationService.validate(declared, profile); + + // A validator cannot see text hidden behind artifact markers, so a clean verdict over + // suppressed content would be a false claim. + boolean honest = !taggingResult.isContentSuppressed(); + + if (validation.compliant() && honest) { + log.info("{} conversion passed validation", profile.displayName()); + return new PdfUaConversionOutcome( + declared, true, validation, summary(taggingResult), warnings); + } + + byte[] undeclared; + try (PDDocument document = load(declared)) { + tagger.withdrawConformance(document); + undeclared = save(document); + } + + if (!validation.compliant()) { + warnings.add( + "The document could not be made " + + profile.displayName() + + " conformant, so no conformance claim was written. " + + validation.totalFailures() + + " automated check(s) still fail."); + } + log.info( + "{} conversion left undeclared: {} failures, suppressedText={}", + profile.displayName(), + validation.totalFailures(), + !honest); + return new PdfUaConversionOutcome( + undeclared, false, validation, summary(taggingResult), warnings); + } + + private static PdfUaConversionOutcome.TaggingSummary summary(TaggingResult result) { + return new PdfUaConversionOutcome.TaggingSummary( + result.isRebuilt(), + result.getTaggedElements(), + result.getArtifacts(), + result.getFiguresNeedingAlt()); + } + + /** + * Tagging rewrites the content streams a signature covers, so the conversion still runs but the + * caller has to know the signature will no longer verify. + */ + private static void warnSignatures(PDDocument document, List warnings) { + int signatures = document.getSignatureDictionaries().size(); + if (signatures > 0) { + warnings.add( + signatures + + " digital signature(s) will stop verifying: tagging rewrites the" + + " content streams they cover. Convert first, then re-sign."); + } + } + + /** Replaces PDFBox's "incorrect password" wording, baffling when the caller supplied none. */ + private PDDocument load(byte[] bytes) throws IOException { + try { + // The factory spills large documents to a temp-file cache instead of the heap. + return pdfDocumentFactory.load(bytes); + } catch (IOException | RuntimeException e) { + // The factory wraps the parse failure, so check the cause chain rather than the type. + if (mentionsPassword(e)) { + throw new IOException( + "This PDF is encrypted. Remove the password before converting it to" + + " PDF/UA.", + e); + } + throw e; + } + } + + private static boolean mentionsPassword(Throwable error) { + for (Throwable cause = error; cause != null; cause = cause.getCause()) { + if (cause instanceof InvalidPasswordException) { + return true; + } + String message = cause.getMessage(); + if (message != null) { + String lower = message.toLowerCase(Locale.ROOT); + if (lower.contains("password") || lower.contains("decrypt")) { + return true; + } + } + } + return false; + } + + /** XFA is forbidden by PDF/UA-1 clause 7.15; encrypted or huge files cannot be restructured. */ + /** + * Under KEEP nothing rebuilds a tree, so if the embedder deleted one we would hand back an + * untagged document. Fonts are not worth the whole structure; give the tags back instead. + */ + private byte[] keepTagsOverFonts( + byte[] input, + byte[] embedded, + SourceFacts facts, + TaggingOptions options, + List warnings) + throws IOException { + if (options.getExistingTags() != TaggingOptions.ExistingTags.KEEP + || !facts.hasUsableTree() + || embedded == input) { + return embedded; + } + boolean survived; + try (PDDocument rewritten = load(embedded)) { + survived = PdfUaTagger.hasUsableStructureTree(rewritten); + } + if (survived) { + return embedded; + } + warnings.add( + "Embedding the missing fonts would have deleted the document's existing tags, so" + + " the tags were kept and the fonts left unembedded. Turn off font" + + " embedding to silence this, or rebuild the tags to embed them."); + return input; + } + + /** + * Checks that must see the document as the author wrote it. Font embedding strips XFA, so + * running this afterwards would let a dynamic form through unnoticed, and it would push a + * document we are about to reject through the whole embedder first. + */ + private static void rejectUnsupportedSource(PDDocument document) throws IOException { + if (document.getNumberOfPages() > MAX_PAGES) { + throw new IOException( + "This PDF has " + + document.getNumberOfPages() + + " pages. PDF/UA conversion is limited to " + + MAX_PAGES + + " pages; split it first."); + } + PDAcroForm form = document.getDocumentCatalog().getAcroForm(); + if (form != null && form.xfaIsDynamic()) { + throw new IOException( + "Dynamic XFA forms are not permitted by PDF/UA. Flatten the form first."); + } + } + + /** + * Deliberately checked on the working document rather than the source. Permissions-only + * encryption with an empty user password is common in published documents, the embedder + * resolves it, and those files convert usefully; rejecting them up front would fail a document + * for a password its author never set. + */ + private static void rejectEncrypted(PDDocument document) throws IOException { + if (document.isEncrypted()) { + throw new IOException( + "Encrypted PDFs cannot be converted to PDF/UA. Remove the password first."); + } + } + + private static byte[] save(PDDocument document) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + return out.toByteArray(); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfUaValidationService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfUaValidationService.java new file mode 100644 index 0000000000..18d9fcaee7 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfUaValidationService.java @@ -0,0 +1,245 @@ +package stirling.software.proprietary.service.ua; + +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; +import org.verapdf.gf.foundry.VeraGreenfieldFoundryProvider; +import org.verapdf.pdfa.Foundries; +import org.verapdf.pdfa.PDFAParser; +import org.verapdf.pdfa.PDFAValidator; +import org.verapdf.pdfa.flavours.PDFAFlavour; +import org.verapdf.pdfa.results.TestAssertion; +import org.verapdf.pdfa.results.ValidationResult; + +import jakarta.annotation.PostConstruct; + +import lombok.extern.slf4j.Slf4j; + +import stirling.software.proprietary.model.api.ua.AccessibilityIssue; +import stirling.software.proprietary.model.api.ua.UaValidationResult; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; + +/** + * Validates a document against a PDF/UA profile using veraPDF, the oracle a conversion is declared + * against. It checks only the machine-verifiable subset, so a clean result is not "accessible". + */ +@Service +@Slf4j +public class PdfUaValidationService { + + /** Plain-English text and remediability for the clauses users actually hit. */ + private static final Map CLAUSES = buildClauseTable(); + + record ClauseInfo(String message, boolean autoFixable) {} + + @PostConstruct + public void initialise() { + try { + VeraGreenfieldFoundryProvider.initialise(); + } catch (Exception e) { + log.error("Failed to initialise veraPDF for PDF/UA validation", e); + } + } + + public UaValidationResult validate(byte[] pdfBytes, PdfUaProfile profile) { + PDFAFlavour flavour = flavourFor(profile); + try (PDFAParser parser = + Foundries.defaultInstance() + .createParser(new ByteArrayInputStream(pdfBytes), flavour)) { + + PDFAValidator validator = Foundries.defaultInstance().createValidator(flavour, false); + ValidationResult result = validator.validate(parser); + return toResult(profile, result); + + } catch (Exception e) { + log.warn("PDF/UA validation failed for {}: {}", profile.displayName(), e.getMessage()); + AccessibilityIssue issue = new AccessibilityIssue(); + issue.setMessage("Validation could not run: " + e.getMessage()); + issue.setSeverity("error"); + issue.setClause("n/a"); + return new UaValidationResult(profile.displayName(), false, List.of(issue), 0); + } + } + + public static PDFAFlavour flavourFor(PdfUaProfile profile) { + return profile == PdfUaProfile.UA2 ? PDFAFlavour.PDFUA_2 : PDFAFlavour.PDFUA_1; + } + + /** + * Whether the bytes really validate as PDF/A level A for the given part. Tagging is necessary + * for level A but not sufficient, so the claim is only written once veraPDF agrees. + */ + public boolean validatesAsPdfaLevelA(byte[] pdfBytes, int part) { + PDFAFlavour flavour = + switch (part) { + case 1 -> PDFAFlavour.PDFA_1_A; + case 2 -> PDFAFlavour.PDFA_2_A; + case 3 -> PDFAFlavour.PDFA_3_A; + default -> null; + }; + if (flavour == null) { + log.warn("No PDF/A level A flavour for part {}", part); + return false; + } + try (PDFAParser parser = + Foundries.defaultInstance() + .createParser(new ByteArrayInputStream(pdfBytes), flavour)) { + PDFAValidator validator = Foundries.defaultInstance().createValidator(flavour, false); + return validator.validate(parser).isCompliant(); + } catch (Exception e) { + log.warn("Level A validation could not run: {}", e.getMessage()); + return false; + } + } + + /** + * Groups repeated failures of the same rule so a report lists issues, not thousands of lines. + */ + private static UaValidationResult toResult(PdfUaProfile profile, ValidationResult result) { + Map grouped = new LinkedHashMap<>(); + int total = 0; + + for (TestAssertion assertion : result.getTestAssertions()) { + if (assertion.getStatus() != TestAssertion.Status.FAILED) { + continue; + } + total++; + String clause = + assertion.getRuleId() != null ? assertion.getRuleId().getClause() : "unknown"; + int test = assertion.getRuleId() != null ? assertion.getRuleId().getTestNumber() : 0; + String key = clause + "-" + test; + + AccessibilityIssue issue = + grouped.computeIfAbsent( + key, + k -> { + AccessibilityIssue created = new AccessibilityIssue(); + created.setClause(clause); + created.setTestNumber(String.valueOf(test)); + created.setSeverity("error"); + ClauseInfo info = lookupClause(clause); + created.setMessage( + info != null ? info.message() : assertion.getMessage()); + created.setTechnicalMessage(assertion.getMessage()); + created.setAutoFixable(info != null && info.autoFixable()); + created.setSpecification(profile.displayName()); + return created; + }); + issue.setOccurrences(issue.getOccurrences() + 1); + if (issue.getLocation() == null && assertion.getLocation() != null) { + issue.setLocation(assertion.getLocation().toString()); + } + } + + List issues = new ArrayList<>(grouped.values()); + return new UaValidationResult( + profile.displayName(), result.isCompliant() && total == 0, issues, total); + } + + /** + * Finds the most specific entry covering a clause by walking up the dotted hierarchy. String + * prefixes would be wrong: {@code 7.1} prefixes {@code 7.18.1} without being its ancestor. + */ + static ClauseInfo lookupClause(String clause) { + if (clause == null) { + return null; + } + String current = clause; + while (!current.isEmpty()) { + ClauseInfo info = CLAUSES.get(current); + if (info != null) { + return info; + } + int dot = current.lastIndexOf('.'); + if (dot < 0) { + return null; + } + current = current.substring(0, dot); + } + return null; + } + + private static Map buildClauseTable() { + Map table = new LinkedHashMap<>(); + table.put( + "7.1", + new ClauseInfo( + "Document is not tagged, or some content is neither tagged nor marked as an artifact.", + true)); + table.put( + "7.2", + new ClauseInfo( + "Text cannot be mapped to Unicode, or the document language is not declared.", + true)); + table.put( + "7.3", + new ClauseInfo("An image or graphic has no alternative description.", false)); + table.put( + "7.4", + new ClauseInfo( + "Heading levels skip a level, or headings are nested incorrectly.", true)); + table.put( + "7.5", + new ClauseInfo("A table is missing header cells or header associations.", false)); + table.put( + "7.6", new ClauseInfo("A list is not structured as list items with bodies.", true)); + table.put( + "7.7", + new ClauseInfo("A mathematical expression has no alternative description.", false)); + table.put( + "7.8", + new ClauseInfo("Running heads or page numbers are not marked as artifacts.", true)); + table.put("7.9", new ClauseInfo("A note is missing a unique identifier.", true)); + // Tagging does not touch optional content groups, so this needs the authoring tool. + table.put("7.10", new ClauseInfo("An optional content group has no name.", false)); + // The attachment's own /AFRelationship and /Desc are not something tagging can supply. + table.put( + "7.11", + new ClauseInfo( + "An embedded file is missing its relationship or description.", false)); + table.put( + "7.15", + new ClauseInfo( + "The document uses a dynamic XFA form, which PDF/UA does not allow.", + false)); + table.put( + "7.16", + new ClauseInfo( + "Security settings prevent assistive technology from reading the content.", + true)); + table.put("7.17", new ClauseInfo("Navigation aids such as page labels are missing.", true)); + table.put( + "7.18", + new ClauseInfo( + "An annotation is missing a description, tab order, or structure entry.", + true)); + table.put( + "7.20", + new ClauseInfo( + "A form or group XObject is not marked as content or as an artifact.", + false)); + // Most font defects (CIDFont, CMap, metrics, encoding) need the font itself repaired. + table.put( + "7.21", + new ClauseInfo("A font in the document does not meet PDF/UA rules.", false)); + // The one font defect embedding does fix. + table.put("7.21.4.1", new ClauseInfo("A font used in the document is not embedded.", true)); + // ToUnicode gaps need the font itself repaired, which embedding does not do. + table.put( + "7.21.7", + new ClauseInfo( + "A font does not map every character it uses to Unicode, so extracted text" + + " may be wrong.", + false)); + table.put( + "5", + new ClauseInfo( + "The document does not declare PDF/UA conformance in its XMP metadata.", + true)); + return table; + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfaAccessibilityService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfaAccessibilityService.java new file mode 100644 index 0000000000..5a13b4ab65 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/ua/PdfaAccessibilityService.java @@ -0,0 +1,297 @@ +package stirling.software.proprietary.service.ua; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdfwriter.compress.CompressParameters; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.xmpbox.XMPMetadata; +import org.apache.xmpbox.schema.PDFAExtensionSchema; +import org.apache.xmpbox.schema.PDFAIdentificationSchema; +import org.apache.xmpbox.type.AbstractStructuredType; +import org.apache.xmpbox.type.ArrayProperty; +import org.apache.xmpbox.type.Cardinality; +import org.apache.xmpbox.type.PDFAPropertyType; +import org.apache.xmpbox.type.PDFASchemaType; +import org.apache.xmpbox.xml.DomXmpParser; +import org.apache.xmpbox.xml.XmpSerializer; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import stirling.software.common.service.PdfaLevelAServiceInterface; +import stirling.software.proprietary.pdf.ua.PdfUaIdentificationSchema; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.PdfUaTagger; +import stirling.software.proprietary.pdf.ua.TaggingOptions; +import stirling.software.proprietary.pdf.ua.TaggingResult; + +/** + * Raises a PDF/A file from conformance level B to level A, which adds the tagging the PDF/UA tagger + * already does. Must run after Ghostscript, which discards any structure tree it is given. + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class PdfaAccessibilityService implements PdfaLevelAServiceInterface { + + /** + * Matches the PDF/UA converter's own cap; beyond this the structure model exhausts the heap. + */ + private static final int MAX_TAGGABLE_PAGES = 2000; + + private final PdfUaValidationService validationService; + + /** + * Tags a converted PDF/A and marks it conformance A, or returns it unchanged rather than + * claiming level A over untagged content. part is 1 to 3; part 1 keeps its PDF 1.4 version. + */ + public Result upgradeToLevelA(byte[] pdfBytes, int part, String language, String title) { + return upgradeToLevelA(pdfBytes, part, language, title, false); + } + + /** + * @param alsoDeclareUa additionally claim PDF/UA, but only if it validates + */ + @Override + public Result upgradeToLevelA( + byte[] pdfBytes, int part, String language, String title, boolean alsoDeclareUa) { + List warnings = new ArrayList<>(); + try { + byte[] tagged; + TaggingResult taggingResult; + + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + // Tagging holds a model of the whole document; without a cap a large file exhausts + // the heap, and OutOfMemoryError is an Error, so the catch below never sees it. + if (document.getNumberOfPages() > MAX_TAGGABLE_PAGES) { + warnings.add( + "This document has " + + document.getNumberOfPages() + + " pages, more than the " + + MAX_TAGGABLE_PAGES + + " that can be tagged, so it was left at conformance level B."); + return new Result(pdfBytes, false, warnings); + } + TaggingOptions options = + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language(language) + .title(title) + .fallbackTitle(title) + // Fonts were embedded on the PDF/A pass; a rewrite would undo it. + .embedFonts(false) + // PDF/A-1 is defined on PDF 1.4; raising it breaks conformance. + .preservePdfVersion(part == 1) + .existingTags(TaggingOptions.ExistingTags.AUTO) + .build(); + + taggingResult = new PdfUaTagger().tag(document, options); + warnings.addAll(taggingResult.getWarnings()); + tagged = save(document, part); + } + + if (taggingResult.getTaggedElements() == 0 && taggingResult.isRebuilt()) { + warnings.add( + "No taggable content was found, so the file cannot claim PDF/A level A." + + " It remains valid at level B."); + return new Result(pdfBytes, false, warnings); + } + if (taggingResult.isContentSuppressed()) { + warnings.add( + "Some text could not be tagged reliably and was marked as an artifact, so" + + " no level A claim was written. The file remains valid at level B."); + return new Result(tagged, false, warnings); + } + + byte[] declared = setConformance(tagged, part, "A"); + + // Tagging is necessary for level A but not sufficient: Unicode mappings are too. + if (!validationService.validatesAsPdfaLevelA(declared, part)) { + warnings.add( + "The document was tagged but does not validate as PDF/A-" + + part + + "a, so it was left at conformance level B."); + return new Result(setConformance(tagged, part, "B"), false, warnings); + } + + if (alsoDeclareUa) { + byte[] withUa = declarePdfUaAlongsidePdfa(declared, part); + var uaResult = validationService.validate(withUa, PdfUaProfile.UA1); + if (uaResult.compliant()) { + log.info("Upgraded PDF/A-{} to level A and declared PDF/UA", part); + return new Result(withUa, true, warnings); + } + // The archival upgrade stands on its own; only the accessibility claim is dropped. + warnings.add( + "PDF/UA was requested alongside PDF/A but " + + uaResult.totalFailures() + + " accessibility check(s) still fail, so no PDF/UA claim was" + + " written. The file is valid PDF/A-" + + part + + "a."); + } + + log.info("Upgraded PDF/A-{} to conformance level A", part); + return new Result(declared, true, warnings); + + } catch (Exception e) { + log.warn("Could not upgrade to PDF/A level A: {}", e.getMessage()); + warnings.add( + "Level A upgrade failed (" + + e.getMessage() + + "), so the file was left at conformance level B."); + return new Result(pdfBytes, false, warnings); + } + } + + /** + * Declares PDF/UA alongside PDF/A in one file. The extension schema is required: PDF/A forbids + * XMP properties no schema describes, and XMPBox has none for {@code pdfuaid}. + */ + static byte[] declarePdfUaAlongsidePdfa(byte[] pdfBytes, int part) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + XMPMetadata xmp = parseOrCreate(document); + + PdfUaIdentificationSchema identification = new PdfUaIdentificationSchema(xmp); + identification.setPart(1); + xmp.addSchema(identification); + + addPdfUaExtensionSchema(xmp); + writeMetadata(document, xmp); + return save(document, part); + } + } + + /** + * Describes the pdfuaid namespace so a PDF/A validator accepts it. Fields are set individually, + * not by subclassing: XMPBox reads the namespace from an annotation, which is not inherited. + */ + private static void addPdfUaExtensionSchema(XMPMetadata xmp) { + PDFAExtensionSchema extension = + (PDFAExtensionSchema) xmp.getSchema(PDFAExtensionSchema.class); + if (extension == null) { + extension = xmp.createAndAddPDFAExtensionSchemaWithDefaultNS(); + } + + PDFAPropertyType partProperty = new PDFAPropertyType(xmp); + addField(xmp, partProperty, PDFAPropertyType.NAME, "part"); + addField(xmp, partProperty, PDFAPropertyType.VALUETYPE, "Integer"); + addField(xmp, partProperty, PDFAPropertyType.CATEGORY, "internal"); + addField( + xmp, + partProperty, + PDFAPropertyType.DESCRIPTION, + "Indicates which part of ISO 14289 the document conforms to"); + + PDFASchemaType schema = new PDFASchemaType(xmp); + addField(xmp, schema, PDFASchemaType.SCHEMA, "PDF/UA Universal Accessibility Schema"); + addField(xmp, schema, PDFASchemaType.NAMESPACE_URI, PdfUaIdentificationSchema.NAMESPACE); + addField(xmp, schema, PDFASchemaType.PREFIX, PdfUaIdentificationSchema.PREFERRED_PREFIX); + + ArrayProperty properties = + xmp.getTypeMapping() + .createArrayProperty( + schema.getNamespace(), + schema.getPrefix(), + PDFASchemaType.PROPERTY, + Cardinality.Seq); + properties.getContainer().addProperty(partProperty); + schema.getContainer().addProperty(properties); + + // A freshly created extension schema has no schemas bag yet, so make one. + ArrayProperty schemas = extension.getSchemasProperty(); + if (schemas == null) { + schemas = + xmp.getTypeMapping() + .createArrayProperty( + extension.getNamespace(), + extension.getPrefix(), + PDFAExtensionSchema.SCHEMAS, + Cardinality.Bag); + extension.addProperty(schemas); + } + schemas.getContainer().addProperty(schema); + } + + /** Adds one text field to a structured type, in that type's own namespace. */ + private static void addField( + XMPMetadata xmp, AbstractStructuredType target, String name, String value) { + target.getContainer() + .addProperty( + xmp.getTypeMapping() + .createText( + target.getNamespace(), target.getPrefix(), name, value)); + } + + private static XMPMetadata parseOrCreate(PDDocument document) throws Exception { + PDMetadata existing = document.getDocumentCatalog().getMetadata(); + if (existing == null) { + return XMPMetadata.createXMPMetadata(); + } + try (InputStream in = new ByteArrayInputStream(existing.toByteArray())) { + DomXmpParser parser = new DomXmpParser(); + parser.setStrictParsing(false); + return parser.parse(in); + } + } + + private static void writeMetadata(PDDocument document, XMPMetadata xmp) throws Exception { + ByteArrayOutputStream serialised = new ByteArrayOutputStream(); + new XmpSerializer().serialize(xmp, serialised, true); + PDMetadata metadata = new PDMetadata(document); + metadata.importXMPMetadata(serialised.toByteArray()); + document.getDocumentCatalog().setMetadata(metadata); + } + + /** Rewrites {@code pdfaid:conformance} without disturbing the rest of the packet. */ + static byte[] setConformance(byte[] pdfBytes, int part, String conformance) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + PDMetadata existing = document.getDocumentCatalog().getMetadata(); + XMPMetadata xmp; + if (existing != null) { + try (InputStream in = new ByteArrayInputStream(existing.toByteArray())) { + DomXmpParser parser = new DomXmpParser(); + parser.setStrictParsing(false); + xmp = parser.parse(in); + } + } else { + xmp = XMPMetadata.createXMPMetadata(); + } + + PDFAIdentificationSchema identification = + (PDFAIdentificationSchema) xmp.getSchema(PDFAIdentificationSchema.class); + if (identification == null) { + identification = xmp.createAndAddPDFAIdentificationSchema(); + } + identification.setPart(part); + identification.setConformance(conformance); + + ByteArrayOutputStream serialised = new ByteArrayOutputStream(); + new XmpSerializer().serialize(xmp, serialised, true); + PDMetadata metadata = new PDMetadata(document); + metadata.importXMPMetadata(serialised.toByteArray()); + document.getDocumentCatalog().setMetadata(metadata); + + return save(document, part); + } + } + + /** + * Part 1 is saved uncompressed: PDFBox's default object streams need PDF 1.5, which would push + * a PDF/A-1 file off its required 1.4 version. + */ + private static byte[] save(PDDocument document, int part) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save( + out, part == 1 ? CompressParameters.NO_COMPRESSION : new CompressParameters()); + return out.toByteArray(); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/LayoutAnalyzerTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/LayoutAnalyzerTest.java new file mode 100644 index 0000000000..e9aaab0a1a --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/LayoutAnalyzerTest.java @@ -0,0 +1,286 @@ +package stirling.software.proprietary.pdf.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** Unit tests for the heuristics that decide what a run of text means. */ +class LayoutAnalyzerTest { + + private static final BBox A4 = new BBox(0, 0, 595, 842); + + private static TextLineInfo line(String text, float size, float x, float y) { + return line(text, size, x, y, false, 0, 0); + } + + private static TextLineInfo line( + String text, float size, float x, float y, boolean bold, int start, int end) { + List words = new ArrayList<>(); + float cursor = x; + for (String token : text.strip().split("\\s+")) { + float width = token.length() * size * 0.5f; + words.add( + new WordInfo( + token, + new BBox(cursor, y, cursor + width, y + size), + start, + end, + size, + bold)); + cursor += width + size * 0.3f; + } + return new TextLineInfo( + 0, text, new BBox(x, y, cursor, y + size), size, bold, start, end, false, words); + } + + private static PageContent page(List lines) { + return new PageContent(0, lines, List.of(), lines.size(), false, false, false, A4); + } + + @Nested + @DisplayName("body font size") + class BodyFontSize { + + @Test + @DisplayName("weights by characters so one huge title does not skew the baseline") + void weightsByCharacterCount() { + List lines = + List.of( + line("A Very Large Title", 32, 50, 700), + line("Body text line one which is long", 11, 50, 650), + line("Body text line two which is long", 11, 50, 630), + line("Body text line three also long", 11, 50, 610)); + assertEquals(11f, LayoutAnalyzer.bodyFontSize(List.of(page(lines)))); + } + + @Test + @DisplayName("returns zero when there is no text") + void handlesEmptyDocument() { + assertEquals(0f, LayoutAnalyzer.bodyFontSize(List.of(page(List.of())))); + } + } + + @Nested + @DisplayName("heading detection") + class Headings { + + @Test + @DisplayName("assigns distinct sizes to descending levels") + void assignsTiers() { + List lines = + List.of( + line("Title", 24, 50, 800), + line("Chapter", 18, 50, 750), + line("Section", 14, 50, 700), + line("Body text that is long enough to set a baseline", 11, 50, 650)); + Map tiers = LayoutAnalyzer.headingTiers(List.of(page(lines)), 11f); + assertEquals(1, tiers.get(24f)); + assertEquals(2, tiers.get(18f)); + assertEquals(3, tiers.get(14f)); + assertNull(tiers.get(11f), "body size must not be a heading tier"); + } + + @Test + @DisplayName("rejects long lines and full sentences whatever their size") + void rejectsProse() { + assertFalse( + LayoutAnalyzer.isHeadingCandidate( + line("This line ends like a sentence does.", 20, 50, 700)), + "a line ending in a full stop reads as prose"); + assertFalse( + LayoutAnalyzer.isHeadingCandidate( + line( + "one two three four five six seven eight nine ten eleven twelve" + + " thirteen", + 20, + 50, + 700)), + "a long line is body text however large"); + assertTrue(LayoutAnalyzer.isHeadingCandidate(line("Financial Results", 20, 50, 700))); + } + + @Test + @DisplayName("boldness alone never promotes a line to a heading") + void boldIsNotAHeadingSignal() { + List lines = + List.of( + line("Bold Label", 11, 50, 700, true, 0, 0), + line("Body text long enough to set the baseline here", 11, 50, 650)); + assertTrue( + LayoutAnalyzer.headingTiers(List.of(page(lines)), 11f).isEmpty(), + "a bold line at body size is emphasis, not a heading"); + } + + @Test + @DisplayName("rewrites skipped levels so H1 is never followed by H3") + void normalisesSkippedLevels() { + DocumentStructure structure = new DocumentStructure(); + structure.add(new StructBlock(StructType.H1, 0)); + structure.add(new StructBlock(StructType.H3, 0)); + structure.add(new StructBlock(StructType.H4, 0)); + LayoutAnalyzer.normaliseHeadingLevels(structure); + + assertEquals(StructType.H1, structure.getBlocks().get(0).getType()); + assertEquals(StructType.H2, structure.getBlocks().get(1).getType()); + assertEquals(StructType.H3, structure.getBlocks().get(2).getType()); + } + } + + @Nested + @DisplayName("lists") + class Lists { + + @Test + @DisplayName("recognises bullet and ordered markers") + void recognisesMarkers() { + assertTrue(LayoutAnalyzer.startsListItem(line("• First item", 11, 50, 700))); + assertTrue(LayoutAnalyzer.startsListItem(line("- First item", 11, 50, 700))); + assertTrue(LayoutAnalyzer.startsListItem(line("1. First item", 11, 50, 700))); + assertTrue(LayoutAnalyzer.startsListItem(line("a) First item", 11, 50, 700))); + assertFalse(LayoutAnalyzer.startsListItem(line("Ordinary prose here", 11, 50, 700))); + } + } + + @Nested + @DisplayName("table cells") + class Tables { + + @Test + @DisplayName("splits a row at wide gaps but not at ordinary word spacing") + void splitsOnWideGaps() { + List words = + List.of( + new WordInfo("Region", new BBox(50, 700, 90, 711), 0, 0, 11, false), + new WordInfo("name", new BBox(93, 700, 125, 711), 0, 0, 11, false), + new WordInfo("Units", new BBox(250, 700, 285, 711), 1, 1, 11, false)); + TextLineInfo row = + new TextLineInfo( + 0, + "Region name Units", + new BBox(50, 700, 285, 711), + 11, + false, + 0, + 1, + false, + words); + List> cells = LayoutAnalyzer.splitCells(row); + assertEquals(2, cells.size(), "the small gap is a word space, the large one is a cell"); + assertEquals(2, cells.get(0).size()); + assertEquals("Units", cells.get(1).get(0).text()); + } + + @Test + @DisplayName("words sharing an operator cannot become separate cells") + void detectsInseparableWords() { + List shared = + List.of( + new WordInfo("A", new BBox(50, 700, 60, 711), 3, 3, 11, false), + new WordInfo("B", new BBox(250, 700, 260, 711), 3, 3, 11, false)); + TextLineInfo row = + new TextLineInfo( + 0, "A B", new BBox(50, 700, 260, 711), 11, false, 3, 3, false, shared); + assertFalse( + row.wordsAreSeparable(), + "cells drawn by one operator cannot carry separate marked content ids"); + } + } + + @Nested + @DisplayName("running heads") + class RunningHeads { + + @Test + @DisplayName("treats text repeating in the margin band across pages as an artifact") + void findsRepeatedMarginText() { + List pages = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + List lines = + List.of( + line("Confidential Report", 9, 50, 800), + line("Body content for the page", 11, 50, 400), + line("Page " + (i + 1), 9, 300, 20)); + pages.add(new PageContent(i, lines, List.of(), 3, false, false, false, A4)); + } + Map> artifacts = LayoutAnalyzer.repeatedMarginLines(pages); + assertEquals( + 2, artifacts.get(0).size(), "the running head and the folio are artifacts"); + assertTrue( + artifacts.get(0).stream().noneMatch(l -> l.text().contains("Body content")), + "body text must never be demoted to an artifact"); + } + + @Test + @DisplayName("does not treat a one-off margin line as a running head") + void ignoresUniqueMarginText() { + List titles = List.of("Alpha", "Beta", "Gamma", "Delta"); + List pages = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + List lines = + List.of( + line(titles.get(i) + " overview", 9, 50, 800), + line("Body content", 11, 50, 400)); + pages.add(new PageContent(i, lines, List.of(), 2, false, false, false, A4)); + } + assertTrue(LayoutAnalyzer.repeatedMarginLines(pages).get(0).isEmpty()); + } + + @Test + @DisplayName("a large heading high on the page stays a heading, not chrome") + void doesNotDemoteHeadingsNearTheTop() { + List pages = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + List lines = + List.of( + // Masking digits makes these look identical across pages. + line("Section " + (i + 1), 20, 50, 800), + line("Body text long enough to set the baseline", 11, 50, 400)); + pages.add(new PageContent(i, lines, List.of(), 2, false, false, false, A4)); + } + assertTrue( + LayoutAnalyzer.repeatedMarginLines(pages, 11f).get(0).isEmpty(), + "a heading larger than body text is content, wherever it sits"); + } + } + + @Nested + @DisplayName("columns") + class Columns { + + @Test + @DisplayName("detects a gutter when text sits in two balanced blocks") + void detectsTwoColumns() { + List lines = new ArrayList<>(); + for (int i = 0; i < 6; i++) { + lines.add(line("Left column text", 10, 50, 700 - i * 14)); + lines.add(line("Right column text", 10, 320, 700 - i * 14)); + } + assertNotNull(LayoutAnalyzer.detectGutter(page(lines))); + } + + @Test + @DisplayName("does not split a page whose lines span the full width") + void ignoresSingleColumn() { + List lines = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + lines.add( + line( + "A full width line of prose that crosses the centre of the page", + 10, + 50, + 700 - i * 14)); + } + assertNull(LayoutAnalyzer.detectGutter(page(lines))); + } + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/MarkedContentInjectorTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/MarkedContentInjectorTest.java new file mode 100644 index 0000000000..73d4c64817 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/MarkedContentInjectorTest.java @@ -0,0 +1,164 @@ +package stirling.software.proprietary.pdf.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.font.Standard14Fonts; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** Tests the content-stream rewriting that makes tagging possible. */ +class MarkedContentInjectorTest { + + private static byte[] threeLinePdf() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + for (int i = 0; i < 3; i++) { + cs.beginText(); + cs.setFont(font, 12); + cs.newLineAtOffset(50, 700 - i * 20); + cs.showText("Line " + i); + cs.endText(); + } + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + return out.toByteArray(); + } + } + + private static String contentOf(PDDocument document) throws IOException { + try (InputStream in = document.getPage(0).getContents()) { + return new String(in.readAllBytes(), StandardCharsets.ISO_8859_1); + } + } + + @Test + @DisplayName("wraps claimed content in BDC/EMC with a marked content id") + void wrapsClaimedContent() throws Exception { + try (PDDocument document = Loader.loadPDF(threeLinePdf())) { + StructBlock paragraph = new StructBlock(StructType.P, 0); + paragraph.addRange(0, 1); + + int next = + new MarkedContentInjector() + .inject(document, document.getPage(0), List.of(paragraph), 0, true); + + String content = contentOf(document); + assertTrue(content.contains("/P"), "the structure type was not written"); + assertTrue(content.contains("/MCID"), "no marked content id was written"); + assertTrue(content.contains("BDC"), "no marked content sequence was opened"); + assertTrue(content.contains("EMC"), "no marked content sequence was closed"); + assertFalse(paragraph.getMcids().isEmpty(), "the block was given no marked content id"); + assertTrue(next > 0, "the id counter did not advance"); + } + } + + @Test + @DisplayName("marks unclaimed content as an artifact so nothing is left untagged") + void unclaimedContentBecomesArtifact() throws Exception { + try (PDDocument document = Loader.loadPDF(threeLinePdf())) { + StructBlock paragraph = new StructBlock(StructType.P, 0); + paragraph.addRange(0, 0); + + new MarkedContentInjector() + .inject(document, document.getPage(0), List.of(paragraph), 0, true); + + String content = contentOf(document); + assertTrue( + content.contains("/Artifact"), + "content nobody claimed must be marked as an artifact, or PDF/UA clause 7.1" + + " fails"); + } + } + + @Test + @DisplayName("opens and closes sequences in balanced pairs") + void sequencesAreBalanced() throws Exception { + try (PDDocument document = Loader.loadPDF(threeLinePdf())) { + StructBlock first = new StructBlock(StructType.P, 0); + first.addRange(0, 0); + StructBlock second = new StructBlock(StructType.H1, 0); + second.addRange(2, 2); + + new MarkedContentInjector() + .inject(document, document.getPage(0), List.of(first, second), 0, true); + + String content = contentOf(document); + int opens = count(content, "BDC") + count(content, "BMC"); + int closes = count(content, "EMC"); + assertEquals(opens, closes, "every opened sequence must be closed"); + } + } + + @Test + @DisplayName("rewriting does not change what a reader extracts") + void textIsUnchanged() throws Exception { + byte[] original = threeLinePdf(); + String before = extract(original); + + byte[] rewritten; + try (PDDocument document = Loader.loadPDF(original)) { + StructBlock paragraph = new StructBlock(StructType.P, 0); + paragraph.addRange(0, 2); + new MarkedContentInjector() + .inject(document, document.getPage(0), List.of(paragraph), 0, true); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + rewritten = out.toByteArray(); + } + assertEquals(before, extract(rewritten), "marked content operators must not render"); + } + + @Test + @DisplayName("two blocks claiming the same content keep the first, not both") + void overlappingClaimsAreResolved() { + StructBlock first = new StructBlock(StructType.P, 0); + first.addRange(0, 2); + StructBlock second = new StructBlock(StructType.H1, 0); + second.addRange(1, 1); + + Map owners = + MarkedContentInjector.ownersByOrdinal(List.of(first, second)); + assertSame(first, owners.get(1), "the first claim wins so reading order stays unambiguous"); + assertEquals(3, owners.size()); + } + + private static int count(String haystack, String needle) { + int total = 0; + int index = 0; + while ((index = haystack.indexOf(needle, index)) >= 0) { + total++; + index += needle.length(); + } + return total; + } + + private static String extract(byte[] pdf) throws IOException { + try (PDDocument document = Loader.loadPDF(pdf)) { + return new org.apache.pdfbox.text.PDFTextStripper() + .getText(document) + .replaceAll("\\s+", " ") + .strip(); + } + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/MarkedContentSafetyTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/MarkedContentSafetyTest.java new file mode 100644 index 0000000000..29cc18c525 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/MarkedContentSafetyTest.java @@ -0,0 +1,193 @@ +package stirling.software.proprietary.pdf.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.font.Standard14Fonts; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** Regression tests for rewriter damage a validator cannot see, so it still passes validation. */ +class MarkedContentSafetyTest { + + private static String contentOf(PDDocument document) throws IOException { + try (InputStream in = document.getPage(0).getContents()) { + return new String(in.readAllBytes(), StandardCharsets.ISO_8859_1); + } + } + + private static void setContent(PDDocument document, String content) throws IOException { + PDStream stream = new PDStream(document); + try (var out = stream.createOutputStream()) { + out.write(content.getBytes(StandardCharsets.ISO_8859_1)); + } + document.getPage(0).setContents(stream); + } + + private static PDDocument onePage() throws IOException { + PDDocument document = new PDDocument(); + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + cs.beginText(); + cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); + cs.newLineAtOffset(50, 700); + cs.showText("visible"); + cs.endText(); + } + return document; + } + + @Test + @DisplayName("an optional-content layer survives the rebuild, so hidden content stays hidden") + void optionalContentIsPreserved() throws Exception { + try (PDDocument document = onePage()) { + String original = contentOf(document); + setContent(document, "/OC /MC0 BDC\n" + original + "\nEMC\n"); + + StructBlock paragraph = new StructBlock(StructType.P, 0); + paragraph.addRange(0, 0); + new MarkedContentInjector() + .inject(document, document.getPage(0), List.of(paragraph), 0, true); + + String rewritten = contentOf(document); + assertTrue( + rewritten.contains("/OC"), + "the optional-content wrapper was stripped, which would make a hidden" + + " DRAFT/CONFIDENTIAL or redaction layer permanently visible:\n" + + rewritten); + assertEquals( + countOf(rewritten, "BDC") + countOf(rewritten, "BMC"), + countOf(rewritten, "EMC"), + "marked content is unbalanced after preserving the layer"); + } + } + + @Test + @DisplayName("replacement text survives the rebuild so ligatures still read correctly") + void actualTextIsPreserved() throws Exception { + try (PDDocument document = onePage()) { + String original = contentOf(document); + // A generator marks an ffi ligature with what it really spells. + setContent( + document, "/Span <> BDC\n" + original + "\nEMC\n"); + + StructBlock paragraph = new StructBlock(StructType.P, 0); + paragraph.addRange(0, 0); + new MarkedContentInjector() + .inject(document, document.getPage(0), List.of(paragraph), 0, true); + + String rewritten = contentOf(document); + assertTrue( + rewritten.contains("ActualText"), + "dropping ActualText leaves a screen reader announcing the raw glyph:\n" + + rewritten); + assertFalse( + rewritten.contains("/MCID 7"), + "the source's own marked content id is meaningless after a rebuild"); + assertEquals( + countOf(rewritten, "BDC") + countOf(rewritten, "BMC"), + countOf(rewritten, "EMC"), + "marked content is unbalanced after preserving replacement text"); + } + } + + @Test + @DisplayName("a sequence wrapping a fill opens before the path, not inside it") + void markedContentNeverOpensInsideAPathObject() throws Exception { + try (PDDocument document = onePage()) { + setContent(document, "0 0 0 rg\n10 10 50 5 re\nf\n"); + + new MarkedContentInjector().inject(document, document.getPage(0), List.of(), 0, true); + + String rewritten = contentOf(document); + int reAt = rewritten.indexOf(" re"); + int openAt = Math.max(rewritten.indexOf("BMC"), rewritten.indexOf("BDC")); + assertTrue(openAt >= 0, "no sequence was opened at all: " + rewritten); + assertTrue( + openAt < reAt, + "ISO 32000-1 does not permit a marked-content operator inside a path object;" + + " the sequence must open before the path construction:\n" + + rewritten); + } + } + + @Test + @DisplayName("words drawn out of stream order are still claimed, not silently artifacted") + void outOfOrderWordsAreClaimed() { + // A line whose second word on the page was painted first: ordinals 1 then 0. + WordInfo right = new WordInfo("label", new BBox(50, 700, 90, 712), 1, 1, 11, false); + WordInfo left = new WordInfo("value", new BBox(200, 700, 240, 712), 0, 0, 11, false); + TextLineInfo line = + new TextLineInfo( + 0, + "label value", + new BBox(50, 700, 240, 712), + 11, + false, + 0, + 1, + false, + List.of(right, left)); + + PageContent page = + new PageContent( + 0, + List.of(line), + List.of(), + 2, + false, + false, + false, + new BBox(0, 0, 595, 842)); + DocumentStructure structure = new LayoutAnalyzer().analyse(List.of(page)); + + boolean[] claimed = new boolean[2]; + structure.visit( + block -> { + if (block.isArtifact()) { + return; + } + block.getRanges() + .forEach( + r -> { + for (int i = r.start(); i <= r.end() && i < 2; i++) { + claimed[i] = true; + } + }); + }); + assertTrue( + claimed[0] && claimed[1], + "an out-of-order word was left unclaimed and would be hidden from assistive" + + " technology while the file still validated"); + } + + private static int countOf(String haystack, String needle) { + int total = 0; + int index = 0; + while ((index = haystack.indexOf(needle, index)) >= 0) { + total++; + index += needle.length(); + } + return total; + } + + private static byte[] bytes(PDDocument document) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + return out.toByteArray(); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaFormAndDeclarationTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaFormAndDeclarationTest.java new file mode 100644 index 0000000000..bae4ebed9a --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaFormAndDeclarationTest.java @@ -0,0 +1,171 @@ +package stirling.software.proprietary.pdf.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDTextField; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** Covers form-field descriptions, widget nesting and withdrawing a conformance claim. */ +class PdfUaFormAndDeclarationTest { + + /** A document with one named text field and one unnamed one. */ + private static PDDocument formDocument(boolean nameTheSecondField) throws IOException { + PDDocument document = new PDDocument(); + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + + PDAcroForm form = new PDAcroForm(document); + document.getDocumentCatalog().setAcroForm(form); + + PDTextField named = new PDTextField(form); + named.setPartialName("EmailAddress"); + addWidget(named, page, 700); + form.getFields().add(named); + + PDTextField second = new PDTextField(form); + if (nameTheSecondField) { + second.setPartialName("PostCode"); + } + addWidget(second, page, 650); + form.getFields().add(second); + + return document; + } + + private static void addWidget(PDTextField field, PDPage page, float y) throws IOException { + PDAnnotationWidget widget = field.getWidgets().get(0); + PDRectangle rectangle = new PDRectangle(); + rectangle.setLowerLeftX(50); + rectangle.setLowerLeftY(y); + rectangle.setUpperRightX(250); + rectangle.setUpperRightY(y + 18); + widget.setRectangle(rectangle); + widget.setPage(page); + page.getAnnotations().add(widget); + } + + private static String xmpOf(PDDocument document) throws IOException { + var metadata = document.getDocumentCatalog().getMetadata(); + assertNotNull(metadata, "no XMP packet"); + return new String(metadata.toByteArray(), StandardCharsets.UTF_8); + } + + @Test + @DisplayName("a form field gets its tooltip from its own name, not an invented one") + void derivesTooltipFromFieldName() throws Exception { + try (PDDocument document = formDocument(true)) { + List warnings = + new PdfUaMetadataWriter() + .applyDocumentRequirements(document, "Form", "en", PdfUaProfile.UA1); + + PDAcroForm form = document.getDocumentCatalog().getAcroForm(); + assertEquals("EmailAddress", form.getField("EmailAddress").getAlternateFieldName()); + assertEquals("PostCode", form.getField("PostCode").getAlternateFieldName()); + assertTrue(warnings.isEmpty(), "nothing needed reporting: " + warnings); + } + } + + @Test + @DisplayName("a field with no name is reported rather than given a placeholder tooltip") + void reportsUnnameableField() throws Exception { + try (PDDocument document = formDocument(false)) { + List warnings = + new PdfUaMetadataWriter() + .applyDocumentRequirements(document, "Form", "en", PdfUaProfile.UA1); + assertEquals(1, warnings.size()); + assertTrue(warnings.get(0).contains("form field"), warnings.get(0)); + } + } + + @Test + @DisplayName("an existing description is never overwritten") + void keepsExistingDescription() throws Exception { + try (PDDocument document = formDocument(true)) { + PDAcroForm form = document.getDocumentCatalog().getAcroForm(); + form.getField("EmailAddress").setAlternateFieldName("Your email address"); + + new PdfUaMetadataWriter() + .applyDocumentRequirements(document, "Form", "en", PdfUaProfile.UA1); + assertEquals( + "Your email address", form.getField("EmailAddress").getAlternateFieldName()); + } + } + + @Test + @DisplayName("widget annotations are nested inside a Form structure element") + void widgetsAreNestedInFormElements() throws Exception { + try (PDDocument document = formDocument(true)) { + DocumentStructure structure = new DocumentStructure(); + StructBlock paragraph = new StructBlock(StructType.P, 0); + paragraph.getMcids().add(0); + structure.add(paragraph); + + new StructTreeWriter().write(document, structure, PdfUaProfile.UA1); + + var root = document.getDocumentCatalog().getStructureTreeRoot(); + assertTrue( + typesUnder(root).contains("Form"), + "clause 7.18.4 requires a widget to sit inside a Form element, found: " + + typesUnder(root)); + } + } + + private static List typesUnder(PDStructureNode node) { + List types = new java.util.ArrayList<>(); + for (Object kid : node.getKids()) { + if (kid instanceof PDStructureElement element) { + types.add(element.getStructureType()); + types.addAll(typesUnder(element)); + } + } + return types; + } + + @Test + @DisplayName("withdrawing conformance removes the claim but keeps the other metadata") + void withdrawingConformanceRemovesOnlyTheClaim() throws Exception { + try (PDDocument document = new PDDocument()) { + document.addPage(new PDPage()); + PdfUaTagger tagger = new PdfUaTagger(); + PdfUaMetadataWriter writer = new PdfUaMetadataWriter(); + + writer.applyDocumentRequirements(document, "Kept Title", "en-GB", PdfUaProfile.UA1); + writer.declareConformance(document, PdfUaProfile.UA1); + assertTrue(xmpOf(document).contains("pdfuaid")); + + tagger.withdrawConformance(document); + + String xmp = xmpOf(document); + assertFalse(xmp.contains("pdfuaid"), "the conformance claim should be gone"); + assertTrue(xmp.contains("Kept Title"), "the title should survive"); + assertEquals("en-GB", document.getDocumentCatalog().getLanguage()); + } + } + + @Test + @DisplayName("withdrawing conformance on a document that never claimed it is harmless") + void withdrawingIsIdempotent() throws Exception { + try (PDDocument document = new PDDocument()) { + document.addPage(new PDPage()); + new PdfUaMetadataWriter() + .applyDocumentRequirements(document, "Title", "en", PdfUaProfile.UA1); + new PdfUaTagger().withdrawConformance(document); + assertFalse(xmpOf(document).contains("pdfuaid")); + } + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaLanguageTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaLanguageTest.java new file mode 100644 index 0000000000..ed05e8de0b --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaLanguageTest.java @@ -0,0 +1,79 @@ +package stirling.software.proprietary.pdf.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * A relabelled language is invisible to every validator, so the tagger must not guess over one the + * document already declares. + */ +class PdfUaLanguageTest { + + private static PDDocument documentWithLanguage(String language) { + PDDocument document = new PDDocument(); + document.addPage(new PDPage()); + if (language != null) { + document.getDocumentCatalog().setLanguage(language); + } + return document; + } + + private static TaggingOptions.TaggingOptionsBuilder options() { + return TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("Rapport") + .embedFonts(false); + } + + @Test + @DisplayName("keeps the language the document already declares") + void keepsExistingLanguage() throws Exception { + try (PDDocument document = documentWithLanguage("fr-FR")) { + TaggingResult result = new PdfUaTagger().tag(document, options().build()); + + assertEquals("fr-FR", document.getDocumentCatalog().getLanguage()); + assertTrue( + result.getWarnings().stream().anyMatch(w -> w.contains("fr-FR")), + "ignoring the requested language must be reported: " + result.getWarnings()); + } + } + + @Test + @DisplayName("applies the requested language when the document declares none") + void fillsInMissingLanguage() throws Exception { + try (PDDocument document = documentWithLanguage(null)) { + new PdfUaTagger().tag(document, options().build()); + + assertEquals("en-GB", document.getDocumentCatalog().getLanguage()); + } + } + + @Test + @DisplayName("replaces the declared language only when the caller asks") + void overridesOnRequest() throws Exception { + try (PDDocument document = documentWithLanguage("fr-FR")) { + new PdfUaTagger().tag(document, options().overrideLanguage(true).build()); + + assertEquals("en-GB", document.getDocumentCatalog().getLanguage()); + } + } + + @Test + @DisplayName("keeps the existing language when an existing structure tree is left alone") + void keepsExistingLanguageWithoutRebuilding() throws Exception { + try (PDDocument document = documentWithLanguage("de-DE")) { + new PdfUaTagger() + .tag( + document, + options().existingTags(TaggingOptions.ExistingTags.KEEP).build()); + + assertEquals("de-DE", document.getDocumentCatalog().getLanguage()); + } + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaMetadataWriterTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaMetadataWriterTest.java new file mode 100644 index 0000000000..f953488b03 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaMetadataWriterTest.java @@ -0,0 +1,137 @@ +package stirling.software.proprietary.pdf.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** Tests the document-level requirements that have nothing to do with tagging. */ +class PdfUaMetadataWriterTest { + + private static PDDocument twoPageDocument() { + PDDocument document = new PDDocument(); + document.addPage(new PDPage()); + document.addPage(new PDPage()); + return document; + } + + private static String xmpOf(PDDocument document) throws IOException { + var metadata = document.getDocumentCatalog().getMetadata(); + assertNotNull(metadata, "no XMP packet was written"); + return new String(metadata.toByteArray(), StandardCharsets.UTF_8); + } + + @Test + @DisplayName("sets title, language, tab order and the display-title flag") + void appliesDocumentRequirements() throws Exception { + try (PDDocument document = twoPageDocument()) { + new PdfUaMetadataWriter() + .applyDocumentRequirements( + document, "Annual Report", "en-GB", PdfUaProfile.UA1); + + assertEquals("en-GB", document.getDocumentCatalog().getLanguage()); + assertEquals("Annual Report", document.getDocumentInformation().getTitle()); + assertTrue( + document.getDocumentCatalog().getViewerPreferences().displayDocTitle(), + "without DisplayDocTitle a viewer shows the filename instead of the title"); + + for (PDPage page : document.getPages()) { + assertEquals( + "S", + page.getCOSObject().getNameAsString(COSName.getPDFName("Tabs")), + "clause 7.18.1 requires an explicit tab order on every page"); + } + } + } + + @Test + @DisplayName("writes dc:title into the XMP packet, not just the info dictionary") + void writesDublinCoreTitle() throws Exception { + try (PDDocument document = twoPageDocument()) { + new PdfUaMetadataWriter() + .applyDocumentRequirements( + document, "Annual Report", "en-GB", PdfUaProfile.UA1); + assertTrue(xmpOf(document).contains("Annual Report")); + } + } + + @Test + @DisplayName("does not declare conformance as part of applying requirements") + void doesNotDeclareEarly() throws Exception { + try (PDDocument document = twoPageDocument()) { + new PdfUaMetadataWriter() + .applyDocumentRequirements(document, "Report", "en", PdfUaProfile.UA1); + assertFalse( + xmpOf(document).contains("pdfuaid"), + "the conformance claim must wait until validation has passed"); + } + } + + @Test + @DisplayName("declaring conformance writes pdfuaid with the right part") + void declaresConformance() throws Exception { + try (PDDocument document = twoPageDocument()) { + PdfUaMetadataWriter writer = new PdfUaMetadataWriter(); + writer.applyDocumentRequirements(document, "Report", "en", PdfUaProfile.UA1); + writer.declareConformance(document, PdfUaProfile.UA1); + + String xmp = xmpOf(document); + assertTrue(xmp.contains("pdfuaid"), "no PDF/UA identifier was written"); + assertTrue(xmp.contains("part"), "no conformance part was written"); + } + } + + @Test + @DisplayName("UA-2 raises the PDF version to 2.0") + void ua2RaisesVersion() throws Exception { + try (PDDocument document = twoPageDocument()) { + new PdfUaMetadataWriter() + .applyDocumentRequirements(document, "Report", "en", PdfUaProfile.UA2); + assertEquals(2.0f, document.getVersion()); + } + } + + @Test + @DisplayName("keeps an existing title when none is supplied") + void keepsExistingTitle() throws Exception { + try (PDDocument document = twoPageDocument()) { + var info = document.getDocumentInformation(); + info.setTitle("Original Title"); + document.setDocumentInformation(info); + + new PdfUaMetadataWriter() + .applyDocumentRequirements(document, null, "en", PdfUaProfile.UA1); + assertEquals("Original Title", document.getDocumentInformation().getTitle()); + } + } + + @Test + @DisplayName("survives a round trip through save and reload") + void survivesRoundTrip() throws Exception { + byte[] saved; + try (PDDocument document = twoPageDocument()) { + PdfUaMetadataWriter writer = new PdfUaMetadataWriter(); + writer.applyDocumentRequirements(document, "Round Trip", "fr-FR", PdfUaProfile.UA1); + writer.declareConformance(document, PdfUaProfile.UA1); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + saved = out.toByteArray(); + } + try (PDDocument reloaded = Loader.loadPDF(saved)) { + assertEquals("fr-FR", reloaded.getDocumentCatalog().getLanguage()); + assertEquals("Round Trip", reloaded.getDocumentInformation().getTitle()); + assertTrue(xmpOf(reloaded).contains("pdfuaid")); + } + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaModelTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaModelTest.java new file mode 100644 index 0000000000..de189f2f74 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/PdfUaModelTest.java @@ -0,0 +1,160 @@ +package stirling.software.proprietary.pdf.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** Tests for the small types the tagger is built from. */ +class PdfUaModelTest { + + @Nested + @DisplayName("structure types") + class Types { + + @Test + @DisplayName("maps levels to heading tags and back") + void headingLevelsRoundTrip() { + for (int level = 1; level <= 6; level++) { + assertEquals(level, StructType.heading(level).headingLevel()); + assertEquals("H" + level, StructType.heading(level).tag()); + } + } + + @Test + @DisplayName("clamps out-of-range levels rather than throwing") + void clampsLevels() { + assertEquals(StructType.H1, StructType.heading(0)); + assertEquals(StructType.H1, StructType.heading(-3)); + assertEquals(StructType.H6, StructType.heading(9)); + } + + @Test + @DisplayName("reports zero for types that are not headings") + void nonHeadingsHaveNoLevel() { + assertEquals(0, StructType.P.headingLevel()); + assertFalse(StructType.TABLE.isHeading()); + } + } + + @Nested + @DisplayName("markable operators") + class Markable { + + @ParameterizedTest + @ValueSource(strings = {"Tj", "TJ", "'", "\"", "Do", "BI", "S", "f", "f*", "B", "sh"}) + @DisplayName("counts text, XObjects and path painting") + void counted(String operator) { + assertTrue(MarkableOp.isMarkableOperator(operator), operator + " should be markable"); + } + + @ParameterizedTest + @ValueSource(strings = {"q", "Q", "cm", "BT", "ET", "Tf", "Td", "n", "W", "gs", "re"}) + @DisplayName("ignores operators that paint nothing") + void notCounted(String operator) { + assertFalse( + MarkableOp.isMarkableOperator(operator), operator + " should not be markable"); + } + + @Test + @DisplayName("n ends a path without painting, so it is not content") + void pathEndIsNotPainting() { + assertFalse(MarkableOp.isPathPainting("n")); + assertTrue(MarkableOp.isPathPainting("f")); + } + } + + @Nested + @DisplayName("profiles") + class Profiles { + + @Test + @DisplayName("parses the shapes a caller might send") + void parsesRequestValues() { + assertEquals(PdfUaProfile.UA1, PdfUaProfile.fromRequest("ua1")); + assertEquals(PdfUaProfile.UA1, PdfUaProfile.fromRequest(null)); + assertEquals(PdfUaProfile.UA1, PdfUaProfile.fromRequest("")); + assertEquals(PdfUaProfile.UA1, PdfUaProfile.fromRequest("nonsense")); + assertEquals(PdfUaProfile.UA2, PdfUaProfile.fromRequest("ua2")); + assertEquals(PdfUaProfile.UA2, PdfUaProfile.fromRequest("PDF/UA-2")); + } + + @Test + @DisplayName("UA-2 requires PDF 2.0") + void ua2NeedsPdf2() { + assertEquals(2.0f, PdfUaProfile.UA2.pdfVersion()); + assertEquals(1.7f, PdfUaProfile.UA1.pdfVersion()); + } + } + + @Nested + @DisplayName("bounding boxes") + class Boxes { + + @Test + @DisplayName("union of an empty box is the other box") + void unionWithEmpty() { + BBox box = new BBox(10, 10, 20, 20); + assertEquals(box, box.union(BBox.EMPTY)); + assertEquals(box, BBox.EMPTY.union(box)); + } + + @Test + @DisplayName("union covers both boxes") + void unionCoversBoth() { + BBox union = new BBox(0, 0, 10, 10).union(new BBox(20, 5, 30, 25)); + assertEquals(new BBox(0, 0, 30, 25), union); + } + + @Test + @DisplayName("reports horizontal overlap as a fraction of the narrower box") + void overlapIsRelative() { + BBox wide = new BBox(0, 0, 100, 10); + BBox narrow = new BBox(40, 0, 60, 10); + assertEquals(1.0f, wide.horizontalOverlap(narrow)); + assertEquals(0f, wide.horizontalOverlap(new BBox(200, 0, 220, 10))); + } + } + + @Nested + @DisplayName("structure blocks") + class Blocks { + + @Test + @DisplayName("counts content across the whole subtree") + void countsDescendantContent() { + StructBlock table = new StructBlock(StructType.TABLE, 0); + StructBlock row = new StructBlock(StructType.TR, 0); + StructBlock cell = new StructBlock(StructType.TD, 0); + cell.addRange(3, 5); + row.addChild(cell); + table.addChild(row); + assertEquals(3, table.contentCount()); + } + + @Test + @DisplayName("collects text in tree order") + void collectsText() { + StructBlock list = new StructBlock(StructType.L, 0); + StructBlock first = new StructBlock(StructType.LI, 0); + first.setText("one"); + StructBlock second = new StructBlock(StructType.LI, 0); + second.setText("two"); + list.addChild(first).addChild(second); + assertEquals("one two", list.collectText()); + } + + @Test + @DisplayName("an artifact is not a structure element") + void artifactsAreDistinct() { + StructBlock artifact = StructBlock.artifact(ArtifactType.PAGINATION, 0); + assertTrue(artifact.isArtifact()); + assertEquals("Pagination", artifact.getArtifactType().subtype()); + } + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/VectorAndHeadingTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/VectorAndHeadingTest.java new file mode 100644 index 0000000000..aa1b13e3b9 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/pdf/ua/VectorAndHeadingTest.java @@ -0,0 +1,155 @@ +package stirling.software.proprietary.pdf.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** Tests the heuristics that decide what counts as a drawing and what counts as a heading. */ +class VectorAndHeadingTest { + + private static final BBox A4 = new BBox(0, 0, 595, 842); + + private static TextLineInfo line(String text, float size, float x, float y) { + List words = new ArrayList<>(); + float cursor = x; + for (String token : text.strip().split("\\s+")) { + float width = token.length() * size * 0.5f; + words.add( + new WordInfo( + token, + new BBox(cursor, y, cursor + width, y + size), + 0, + 0, + size, + false)); + cursor += width + size * 0.3f; + } + return new TextLineInfo( + 0, text, new BBox(x, y, cursor, y + size), size, false, 0, 0, false, words); + } + + private static MarkableOp vector(int ordinal, BBox box) { + return new MarkableOp(ordinal, MarkableOp.Kind.VECTOR, box, null); + } + + private static DocumentStructure analyse(List lines, List ops) { + PageContent page = new PageContent(0, lines, ops, ops.size(), false, false, false, A4); + return new LayoutAnalyzer().analyse(List.of(page)); + } + + private static long countOf(DocumentStructure structure, StructType type) { + long[] total = {0}; + structure.visit( + block -> { + if (block.getType() == type) { + total[0]++; + } + }); + return total[0]; + } + + @Test + @DisplayName("a cluster of substantial strokes becomes a figure, not silent decoration") + void chartBecomesAFigure() { + List bars = new ArrayList<>(); + for (int i = 0; i < 6; i++) { + bars.add(vector(i, new BBox(100 + i * 20, 400, 115 + i * 20, 400 + 30 + i * 10))); + } + DocumentStructure structure = analyse(List.of(), bars); + + assertTrue( + countOf(structure, StructType.FIGURE) > 0, + "a bar chart drawn with path operators must not vanish as decoration"); + assertTrue( + structure.figuresWithoutAlt().size() > 0, + "the report must say the chart needs a description"); + } + + @Test + @DisplayName("thin rules and table borders stay artifacts") + void tableRulesStayDecoration() { + List rules = new ArrayList<>(); + for (int i = 0; i < 8; i++) { + rules.add(vector(i, new BBox(60, 700 - i * 20, 540, 701 - i * 20))); + } + DocumentStructure structure = analyse(List.of(), rules); + + assertEquals( + 0, + countOf(structure, StructType.FIGURE), + "horizontal rules are page furniture and must not demand alt text"); + assertEquals(0, structure.figuresWithoutAlt().size()); + } + + @Test + @DisplayName("a lone box is ornament, not a chart") + void singleBoxIsNotAFigure() { + DocumentStructure structure = + analyse(List.of(), List.of(vector(0, new BBox(60, 400, 500, 700)))); + assertEquals(0, countOf(structure, StructType.FIGURE)); + } + + @Test + @DisplayName("shaded table rows behind text are not mistaken for a chart") + void shadedTableRowsAreNotFigures() { + List shading = new ArrayList<>(); + List rows = new ArrayList<>(); + for (int i = 0; i < 6; i++) { + float y = 600 - i * 20; + // A filled row background, tall enough to pass the thinness test. + shading.add(vector(i, new BBox(60, y, 540, y + 16))); + rows.add(line("Expense line item " + i + " amount", 10, 64, y + 3)); + } + DocumentStructure structure = analyse(rows, shading); + + assertEquals( + 0, + countOf(structure, StructType.FIGURE), + "row shading sits behind the text it decorates and is not a drawing"); + } + + @Test + @DisplayName("small print dominating an invoice does not promote addresses to headings") + void smallPrintDoesNotCreateHeadings() { + List lines = new ArrayList<>(); + // Address block at ordinary 11pt. + lines.add(line("Acme Industries Limited", 11, 60, 780)); + lines.add(line("14 Example Street", 11, 60, 765)); + lines.add(line("Manchester M1 2AB", 11, 60, 750)); + // 40 lines of 9pt line-item small print, which dominates the character count. + for (int i = 0; i < 40; i++) { + lines.add(line("Item " + i + " widget assembly part number " + i, 9, 60, 700 - i * 12)); + } + + Map tiers = + LayoutAnalyzer.headingTiers( + List.of(new PageContent(0, lines, List.of(), 0, false, false, false, A4)), + 9f); + assertNull( + tiers.get(11f), + "11pt address lines are body text on an invoice, not headings: " + tiers); + } + + @Test + @DisplayName("a genuinely rare large size is still a heading") + void realHeadingsSurvive() { + List lines = new ArrayList<>(); + lines.add(line("Annual Report", 24, 60, 780)); + for (int i = 0; i < 40; i++) { + lines.add( + line("Body prose line number " + i + " continues here", 11, 60, 700 - i * 12)); + } + Map tiers = + LayoutAnalyzer.headingTiers( + List.of(new PageContent(0, lines, List.of(), 0, false, false, false, A4)), + 11f); + assertEquals(1, tiers.get(24f), "a rare large size is exactly what a heading looks like"); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/policy/config/AdminPolicyManagementAuthorityTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/policy/config/AdminPolicyManagementAuthorityTest.java index 811aae6b4f..0f97fcaf62 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/policy/config/AdminPolicyManagementAuthorityTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/policy/config/AdminPolicyManagementAuthorityTest.java @@ -39,6 +39,18 @@ class AdminPolicyManagementAuthorityTest { assertFalse(authority().canEditPolicies()); } + @Test + void adminMayTriggerPolicies() { + when(userService.isCurrentUserAdmin()).thenReturn(true); + assertTrue(authority().canTriggerPolicies()); + } + + @Test + void nonAdminMayNotTriggerPolicies() { + when(userService.isCurrentUserAdmin()).thenReturn(false); + assertFalse(authority().canTriggerPolicies()); + } + @Test void currentUserTeamIdResolvesFromTheCurrentUsersTeam() { Team team = new Team(); diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/policy/controller/PolicyControllerTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/policy/controller/PolicyControllerTest.java index 84e9998b90..36f5cc221b 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/policy/controller/PolicyControllerTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/policy/controller/PolicyControllerTest.java @@ -2,6 +2,7 @@ package stirling.software.proprietary.policy.controller; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -82,6 +83,8 @@ class PolicyControllerTest { @Mock private stirling.software.proprietary.policy.asset.PolicyAssetCleaner assetCleaner; + @Mock private stirling.software.proprietary.policy.asset.PolicyAssetResolver assetResolver; + @Mock private ProcessedLedger processedLedger; @Mock private TempFileManager tempFileManager; @@ -114,6 +117,7 @@ class PolicyControllerTest { policyTriggerManager, policyOverviewService, assetCleaner, + assetResolver, processedLedger, policyTriggers, applicationProperties, @@ -231,7 +235,7 @@ class PolicyControllerTest { .thenReturn(handle("run-1")); ResponseEntity> response = - controller.run(definitionWithStep(), new PolicyRunFiles()); + controller.run(definitionWithStep(), null, new PolicyRunFiles()); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); assertThat(response.getBody().getJobId()).isEqualTo("run-1"); @@ -244,7 +248,7 @@ class PolicyControllerTest { .thenReturn(handle("run-1")); when(sourceAccessGuard.currentTeamId()).thenReturn(3L); - controller.run(definitionWithStep(), new PolicyRunFiles()); + controller.run(definitionWithStep(), null, new PolicyRunFiles()); verify(docCounter).record(EditorSource.counterKey(3L), 0L); } @@ -254,7 +258,7 @@ class PolicyControllerTest { void rejectsEmptyPipeline() { PipelineDefinition empty = new PipelineDefinition("pipe", List.of(), List.of()); - assertThatThrownBy(() -> controller.run(empty, new PolicyRunFiles())) + assertThatThrownBy(() -> controller.run(empty, null, new PolicyRunFiles())) .isInstanceOf(ResponseStatusException.class) .satisfies( e -> @@ -276,7 +280,7 @@ class PolicyControllerTest { .when(policyValidator) .validateOutput(any()); - assertThatThrownBy(() -> controller.run(definition, new PolicyRunFiles())) + assertThatThrownBy(() -> controller.run(definition, null, new PolicyRunFiles())) .isInstanceOf(ResponseStatusException.class) .satisfies( e -> @@ -284,6 +288,38 @@ class PolicyControllerTest { .isEqualTo(HttpStatus.BAD_REQUEST)); verify(policyRunner, never()).runAdHoc(any(), any(), any()); } + + @Test + @DisplayName("resolves stored assets from the supplied policy when the caller may edit it") + void resolvesStoredAssetsForEditor() throws Exception { + applicationProperties.getSecurity().setEnableLogin(false); // editing allowed + Policy p = policy("pol-1", 1L); + when(policyStore.get("pol-1")).thenReturn(Optional.of(p)); + when(policyAccessGuard.canAccess(p)).thenReturn(true); + when(assetResolver.resolve(eq(p), any())).thenAnswer(inv -> inv.getArgument(1)); + when(policyRunner.runAdHoc(any(), any(), eq(PolicyProgressListener.NOOP))) + .thenReturn(handle("run-1")); + + controller.run(definitionWithStep(), "pol-1", new PolicyRunFiles()); + + verify(assetResolver).resolve(eq(p), any()); + } + + @Test + @DisplayName("does not resolve a policy's stored assets for a caller who cannot edit it") + void skipsStoredAssetsForNonEditor() throws Exception { + // Gating asset resolution to editors keeps a member from rebinding a policy's stored + // asset into an ad-hoc step to read it back. + applicationProperties.getSecurity().setEnableLogin(true); + when(policyManagementAuthority.canEditPolicies()).thenReturn(false); + when(policyRunner.runAdHoc(any(), any(), eq(PolicyProgressListener.NOOP))) + .thenReturn(handle("run-1")); + + controller.run(definitionWithStep(), "pol-1", new PolicyRunFiles()); + + verify(assetResolver, never()).resolve(any(), any()); + verify(policyStore, never()).get(any()); + } } @Nested @@ -295,7 +331,8 @@ class PolicyControllerTest { void returnsEmitter() throws Exception { when(policyRunner.runAdHoc(any(), any(), any())).thenReturn(handle("run-2")); - SseEmitter emitter = controller.runStream(definitionWithStep(), new PolicyRunFiles()); + SseEmitter emitter = + controller.runStream(definitionWithStep(), null, new PolicyRunFiles()); assertThat(emitter).isNotNull(); } @@ -305,7 +342,7 @@ class PolicyControllerTest { void rejectsEmpty() { PipelineDefinition empty = new PipelineDefinition("pipe", List.of(), List.of()); - assertThatThrownBy(() -> controller.runStream(empty, new PolicyRunFiles())) + assertThatThrownBy(() -> controller.runStream(empty, null, new PolicyRunFiles())) .isInstanceOf(ResponseStatusException.class); } } @@ -738,5 +775,78 @@ class PolicyControllerTest { assertThat(((ResponseStatusException) e).getStatusCode()) .isEqualTo(HttpStatus.NOT_FOUND)); } + + @Test + @DisplayName("trigger is forbidden for a team member who cannot manage policies") + void triggerForbiddenForMember() { + // Sweeping a policy's configured sources is a policy-management capability, so being + // in the policy's team is not on its own enough to perform it. + applicationProperties.getSecurity().setEnableLogin(true); + when(policyManagementAuthority.canTriggerPolicies()).thenReturn(false); + + assertThatThrownBy(() -> controller.trigger("a")) + .isInstanceOf(ResponseStatusException.class) + .satisfies( + e -> + assertThat(((ResponseStatusException) e).getStatusCode()) + .isEqualTo(HttpStatus.FORBIDDEN)); + // Rejected before the policy is looked up, so no run starts. + verify(policyRunner, never()).run(any()); + verify(policyStore, never()).get(any()); + } + + @Test + @DisplayName("trigger runs for a caller who may manage policies") + void triggerAllowedForLeader() { + applicationProperties.getSecurity().setEnableLogin(true); + when(policyManagementAuthority.canTriggerPolicies()).thenReturn(true); + Policy p = policy("a", 1L); + when(policyStore.get("a")).thenReturn(Optional.of(p)); + when(policyAccessGuard.canAccess(p)).thenReturn(true); + SweepOutcome outcome = new SweepOutcome(List.of("run-a"), 1, 0, 0, 0); + when(policyRunner.run(p)).thenReturn(outcome); + + ResponseEntity response = controller.trigger("a"); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); + assertThat(response.getBody()).isEqualTo(outcome); + } + + @Test + @DisplayName("trigger skips the role check when login is disabled") + void triggerTrustsTheLocalOperator() { + // Single-user deployments have no roles at all; the gate must not lock them out of + // their + // own sweeps. + applicationProperties.getSecurity().setEnableLogin(false); + Policy p = policy("a", null); + when(policyStore.get("a")).thenReturn(Optional.of(p)); + when(policyAccessGuard.canAccess(p)).thenReturn(true); + SweepOutcome outcome = new SweepOutcome(List.of("run-a"), 1, 0, 0, 0); + when(policyRunner.run(p)).thenReturn(outcome); + + assertThat(controller.trigger("a").getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); + verify(policyManagementAuthority, never()).canTriggerPolicies(); + } + + @Test + @DisplayName("running a policy over the caller's own files stays open to any member") + void storedRunIsNotGatedByRole() { + // Editor enforcement: every member's upload/export runs the team's stored policies on + // their own documents. Gating this the way the sweep is gated would break the editor. + applicationProperties.getSecurity().setEnableLogin(true); + Policy p = policy("a", 1L); + when(policyStore.get("a")).thenReturn(Optional.of(p)); + when(policyAccessGuard.canAccess(p)).thenReturn(true); + when(policyRunner.runWith(eq(p), any(), eq(PolicyProgressListener.NOOP))) + .thenReturn(handle("run-9")); + + ResponseEntity> response = + assertDoesNotThrow(() -> controller.runStoredPolicy("a", new PolicyRunFiles())); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); + verify(policyManagementAuthority, never()).canTriggerPolicies(); + verify(policyManagementAuthority, never()).canEditPolicies(); + } } } diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/AltTextRoundTripTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/AltTextRoundTripTest.java new file mode 100644 index 0000000000..0f8e52d9ec --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/AltTextRoundTripTest.java @@ -0,0 +1,115 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; +import stirling.software.proprietary.controller.api.converters.ConvertPdfToPdfUa; +import stirling.software.proprietary.model.api.ua.AccessibilityReport; +import stirling.software.proprietary.model.api.ua.FigureDescriptor; +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.TaggingOptions; + +/** + * The alt-text loop end to end: the report hands out keys the conversion accepts. The converter + * never invents descriptions, so a caller must be able to supply them. + */ +class AltTextRoundTripTest { + + private static PdfUaConversionService conversion; + private static AccessibilityAuditService audit; + + @BeforeAll + static void setUp() { + PdfUaValidationService validation = new PdfUaValidationService(); + validation.initialise(); + conversion = + new PdfUaConversionService( + validation, + new FontEmbeddingService(), + new CustomPDFDocumentFactory( + org.mockito.Mockito.mock(PdfMetadataService.class))); + audit = new AccessibilityAuditService(validation); + } + + private static TaggingOptions.TaggingOptionsBuilder options() { + return TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("Illustrated") + .embedFonts(false); + } + + @Test + @DisplayName("the report names the figures that need describing, with usable keys") + void reportEnumeratesFigures() throws Exception { + byte[] input = PdfUaTestDocuments.imageDocument(); + AccessibilityReport report = audit.audit(input, PdfUaProfile.UA1); + + assertFalse( + report.getFiguresNeedingDescription().isEmpty(), + "a document with an undescribed image must say which figure needs text"); + + FigureDescriptor figure = report.getFiguresNeedingDescription().get(0); + assertTrue(figure.key().matches("\\d+:\\d+"), "key should be pageIndex:ordinal: " + figure); + assertEquals(1, figure.page(), "pages are reported 1-based for humans"); + assertTrue(figure.width() > 0 && figure.height() > 0, "figure should carry its box"); + } + + @Test + @DisplayName("feeding the report's key back makes the document conform") + void suppliedDescriptionClosesTheLoop() throws Exception { + byte[] input = PdfUaTestDocuments.imageDocument(); + + PdfUaConversionOutcome before = conversion.convert(input, options().build()); + assertFalse(before.declared(), "an undescribed image must block the claim"); + + String key = + audit.audit(input, PdfUaProfile.UA1).getFiguresNeedingDescription().get(0).key(); + PdfUaConversionOutcome after = + conversion.convert( + input, options().altTextByFigure(Map.of(key, "A blue rectangle")).build()); + + assertEquals( + 0, + after.tagging().figuresNeedingAltText(), + "the description supplied against the report's own key was not applied"); + assertTrue(after.declared(), "with every figure described the document should conform"); + } + + @Test + @DisplayName("the request's key=text form parses the way the report emits keys") + void parsesTheWireFormat() { + Map parsed = + ConvertPdfToPdfUa.parseAltText( + "0:12=Bar chart of quarterly revenue\r\n" + + "1:3=Company logo\n" + + " \n" + + "malformed-line\n" + + "2:7=Diagram showing the approval flow = end to end"); + + assertEquals(3, parsed.size(), "blank and malformed lines are skipped: " + parsed); + assertEquals("Bar chart of quarterly revenue", parsed.get("0:12")); + assertEquals("Company logo", parsed.get("1:3")); + assertEquals( + "Diagram showing the approval flow = end to end", + parsed.get("2:7"), + "only the first equals splits, so descriptions may contain one"); + } + + @Test + @DisplayName("no descriptions supplied means none invented") + void emptyInputInventsNothing() { + assertTrue(ConvertPdfToPdfUa.parseAltText(null).isEmpty()); + assertTrue(ConvertPdfToPdfUa.parseAltText(" ").isEmpty()); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUa2ProfileTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUa2ProfileTest.java new file mode 100644 index 0000000000..9cf342824f --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUa2ProfileTest.java @@ -0,0 +1,92 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.TaggingOptions; + +/** PDF/UA-2 is not just a metadata number: it needs PDF 2.0 and namespaced structure types. */ +class PdfUa2ProfileTest { + + private static PdfUaConversionService conversion; + + @BeforeAll + static void setUp() { + PdfUaValidationService validation = new PdfUaValidationService(); + validation.initialise(); + conversion = + new PdfUaConversionService( + validation, + new FontEmbeddingService(), + new CustomPDFDocumentFactory( + org.mockito.Mockito.mock(PdfMetadataService.class))); + } + + private static PdfUaConversionOutcome convertUa2(byte[] input) throws Exception { + return conversion.convert( + input, + TaggingOptions.builder() + .profile(PdfUaProfile.UA2) + .language("en-GB") + .title("UA-2 Document") + .embedFonts(false) + .build()); + } + + @Test + @DisplayName("raises the file to PDF 2.0 and namespaces the structure tree") + void producesPdf2WithNamespaces() throws Exception { + PdfUaConversionOutcome outcome = convertUa2(PdfUaTestDocuments.headingHierarchy()); + + try (PDDocument document = Loader.loadPDF(outcome.pdfBytes())) { + assertEquals(2.0f, document.getVersion(), "UA-2 is defined on PDF 2.0"); + + var root = document.getDocumentCatalog().getStructureTreeRoot(); + assertNotNull(root, "no structure tree was written"); + assertNotNull( + root.getCOSObject().getDictionaryObject(COSName.getPDFName("Namespaces")), + "UA-2 requires the standard structure namespace to be declared"); + } + } + + @Test + @DisplayName("validates against the PDF/UA-2 profile, not the UA-1 one") + void validatesAgainstUa2() throws Exception { + PdfUaConversionOutcome outcome = convertUa2(PdfUaTestDocuments.simpleDocument()); + assertEquals("PDF/UA-2", outcome.validation().profile()); + } + + @Test + @DisplayName("reaches UA-2 conformance and declares it") + void reachesUa2Conformance() throws Exception { + PdfUaConversionOutcome outcome = convertUa2(PdfUaTestDocuments.simpleDocument()); + + String failures = + outcome.validation().issues().stream() + .map(issue -> issue.getClause() + ": " + issue.getTechnicalMessage()) + .collect(java.util.stream.Collectors.joining("; ")); + assertEquals(0, outcome.validation().totalFailures(), "UA-2 checks failed: " + failures); + assertTrue(outcome.declared(), "a conforming UA-2 file must carry the declaration"); + assertTrue(outcome.pdfBytes().length > 0); + } + + @Test + @DisplayName("an illustrated document still cannot claim UA-2 without descriptions") + void undescribedImageBlocksTheUa2Claim() throws Exception { + PdfUaConversionOutcome outcome = convertUa2(PdfUaTestDocuments.imageDocument()); + assertFalse(outcome.declared(), "an undescribed image must block the claim"); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaConversionIntegrationTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaConversionIntegrationTest.java new file mode 100644 index 0000000000..e785957b7b --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaConversionIntegrationTest.java @@ -0,0 +1,231 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; +import stirling.software.proprietary.model.api.ua.AccessibilityIssue; +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.model.api.ua.UaValidationResult; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.TaggingOptions; + +/** + * End-to-end conversion over the fixture corpus, validated with veraPDF. The corpus is deliberately + * varied: what breaks a tagger is rarely the simple case. + */ +class PdfUaConversionIntegrationTest { + + private static PdfUaConversionService service; + + @BeforeAll + static void setUp() { + PdfUaValidationService validation = new PdfUaValidationService(); + validation.initialise(); + service = + new PdfUaConversionService( + validation, + new FontEmbeddingService(), + new CustomPDFDocumentFactory( + org.mockito.Mockito.mock(PdfMetadataService.class))); + } + + /** Fixtures already embed fonts, so the Ghostscript pass is off to keep tests hermetic. */ + private static TaggingOptions options() { + return TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("Test Document") + .embedFonts(false) + .build(); + } + + private static PdfUaConversionOutcome convert(byte[] input) throws IOException { + return service.convert(input, options()); + } + + private static String extractText(byte[] pdf) throws IOException { + try (PDDocument document = Loader.loadPDF(pdf)) { + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setSortByPosition(true); + return PdfUaRealCorpusTest.normalise(stripper.getText(document)); + } + } + + @Test + @DisplayName("every fixture converts without error and gains a structure tree") + void corpusConverts() throws Exception { + Map> corpus = corpus(); + StringBuilder report = new StringBuilder("\nPDF/UA conversion over the fixture corpus\n"); + + for (Map.Entry> entry : corpus.entrySet()) { + byte[] input = + entry.getKey().equals("empty") + ? entry.getValue().call() + : entry.getValue().call(); + PdfUaConversionOutcome outcome = convert(input); + + assertNotNull(outcome.pdfBytes(), entry.getKey() + " produced no output"); + report.append( + String.format( + " %-18s declared=%-5s failures=%-3d elements=%-3d artifacts=%-3d altNeeded=%d%n", + entry.getKey(), + outcome.declared(), + outcome.validation().totalFailures(), + outcome.tagging().taggedElements(), + outcome.tagging().artifacts(), + outcome.tagging().figuresNeedingAltText())); + for (AccessibilityIssue issue : outcome.validation().issues()) { + report.append( + String.format( + " clause %-6s x%-4d %s%n", + issue.getClause(), + issue.getOccurrences(), + issue.getTechnicalMessage())); + } + } + System.out.println(report); + } + + @Test + @DisplayName("tagging never changes the text content of a page") + void textIsPreserved() throws Exception { + for (Map.Entry> entry : corpus().entrySet()) { + byte[] input = entry.getValue().call(); + String before = extractText(input); + String after = extractText(convert(input).pdfBytes()); + assertEquals(before, after, "text changed for fixture " + entry.getKey()); + } + } + + @Test + @DisplayName("a simple document gains a structure tree with headings and paragraphs") + void simpleDocumentIsTagged() throws Exception { + PdfUaConversionOutcome outcome = convert(PdfUaTestDocuments.simpleDocument()); + try (PDDocument document = Loader.loadPDF(outcome.pdfBytes())) { + assertNotNull( + document.getDocumentCatalog().getStructureTreeRoot(), + "no structure tree was written"); + assertTrue( + document.getDocumentCatalog().getMarkInfo() != null + && document.getDocumentCatalog().getMarkInfo().isMarked(), + "MarkInfo/Marked was not set"); + assertEquals("en-GB", document.getDocumentCatalog().getLanguage()); + assertEquals("Test Document", document.getDocumentInformation().getTitle()); + } + assertTrue(outcome.tagging().taggedElements() > 0, "nothing was tagged"); + } + + @Test + @DisplayName("running heads and page numbers become artifacts, not content") + void runningHeadersBecomeArtifacts() throws Exception { + PdfUaConversionOutcome outcome = convert(PdfUaTestDocuments.runningHeadersDocument()); + assertTrue( + outcome.tagging().artifacts() >= 4, + "expected the repeated header on each page to become an artifact, got " + + outcome.tagging().artifacts()); + } + + @Test + @DisplayName("an image is tagged as a figure and reported as needing alt text") + void imagesNeedAltText() throws Exception { + PdfUaConversionOutcome outcome = convert(PdfUaTestDocuments.imageDocument()); + assertEquals(1, outcome.tagging().figuresNeedingAltText()); + assertFalse( + outcome.declared(), + "a document with an undescribed image must not claim conformance"); + } + + @Test + @DisplayName("supplying alt text lets an illustrated document conform") + void suppliedAltTextIsApplied() throws Exception { + byte[] input = PdfUaTestDocuments.imageDocument(); + PdfUaConversionOutcome probe = convert(input); + assertEquals(1, probe.tagging().figuresNeedingAltText()); + + TaggingOptions withAlt = + options().toBuilder() + .altTextByFigure(Map.of(figureKey(input), "A blue rectangle")) + .build(); + PdfUaConversionOutcome outcome = service.convert(input, withAlt); + assertEquals( + 0, + outcome.tagging().figuresNeedingAltText(), + "alt text supplied by the caller was not applied"); + } + + @Test + @DisplayName("an empty document does not crash the converter") + void emptyDocumentIsHandled() throws Exception { + PdfUaConversionOutcome outcome = convert(PdfUaTestDocuments.emptyDocument()); + assertNotNull(outcome.pdfBytes()); + assertFalse(outcome.warnings().isEmpty(), "an empty document should warn"); + } + + @Test + @DisplayName("an un-OCRed scan is reported rather than silently declared conformant") + void scannedDocumentIsNotDeclared() throws Exception { + PdfUaConversionOutcome outcome = convert(PdfUaTestDocuments.scannedDocument()); + assertFalse(outcome.declared(), "a scan with no text layer must not claim conformance"); + } + + @Test + @DisplayName("validation of an untagged document reports the missing structure") + void untaggedDocumentFailsValidation() throws Exception { + PdfUaValidationService validation = new PdfUaValidationService(); + validation.initialise(); + UaValidationResult result = + validation.validate(PdfUaTestDocuments.simpleDocument(), PdfUaProfile.UA1); + assertFalse(result.compliant(), "an untagged document cannot be PDF/UA compliant"); + assertTrue(result.hasIssues()); + } + + /** The key the tagger uses for figure alt text is "pageIndex:firstOrdinal". */ + private static String figureKey(byte[] input) throws IOException { + try (PDDocument document = Loader.loadPDF(input)) { + var pages = + new stirling.software.proprietary.pdf.ua.TaggedContentExtractor() + .extract(document); + for (var page : pages) { + for (var op : page.graphics()) { + return page.pageIndex() + ":" + op.ordinal(); + } + } + } + return "0:0"; + } + + private static Map> corpus() { + Map> corpus = new LinkedHashMap<>(); + corpus.put("simple", PdfUaTestDocuments::simpleDocument); + corpus.put("headings", PdfUaTestDocuments::headingHierarchy); + corpus.put("lists", PdfUaTestDocuments::listDocument); + corpus.put("table", PdfUaTestDocuments::tableDocument); + corpus.put("image", PdfUaTestDocuments::imageDocument); + corpus.put("runningHeads", PdfUaTestDocuments::runningHeadersDocument); + corpus.put("twoColumn", PdfUaTestDocuments::twoColumnDocument); + corpus.put("link", PdfUaTestDocuments::linkDocument); + corpus.put("empty", PdfUaTestDocuments::emptyDocument); + corpus.put("scanned", PdfUaTestDocuments::scannedDocument); + corpus.put("formXObject", PdfUaTestDocuments::formXObjectDocument); + corpus.put("multiStream", PdfUaTestDocuments::multiStreamDocument); + corpus.put("rotated", PdfUaTestDocuments::rotatedDocument); + corpus.put("offsetMediaBox", PdfUaTestDocuments::offsetMediaBoxDocument); + return corpus; + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaHardeningTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaHardeningTest.java new file mode 100644 index 0000000000..cf72013a13 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaHardeningTest.java @@ -0,0 +1,322 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.contentstream.operator.Operator; +import org.apache.pdfbox.pdfparser.PDFStreamParser; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.pdf.ua.PageContent; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.StructBlock; +import stirling.software.proprietary.pdf.ua.StructType; +import stirling.software.proprietary.pdf.ua.TaggingOptions; + +/** Covers the document shapes and failure modes the first round of tests missed. */ +class PdfUaHardeningTest { + + private static PdfUaConversionService service; + + @BeforeAll + static void setUp() { + PdfUaValidationService validation = new PdfUaValidationService(); + validation.initialise(); + service = + new PdfUaConversionService( + validation, + new FontEmbeddingService(), + new CustomPDFDocumentFactory( + org.mockito.Mockito.mock(PdfMetadataService.class))); + } + + private static PdfUaConversionOutcome convert(byte[] input) throws IOException { + return service.convert( + input, + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("Hardening") + .embedFonts(false) + .build()); + } + + private static String extract(byte[] pdf) throws IOException { + try (PDDocument document = Loader.loadPDF(pdf)) { + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setSortByPosition(true); + return PdfUaRealCorpusTest.normalise(stripper.getText(document)); + } + } + + @Nested + @DisplayName("document shapes") + class Shapes { + + @Test + @DisplayName("text inside a form XObject is attributed to the Do and tagged as prose") + void formXObjectTextIsTagged() throws Exception { + byte[] input = PdfUaTestDocuments.formXObjectDocument(); + assertTrue( + extract(input).contains("inside the form XObject"), + "fixture must actually draw text inside a form"); + + PdfUaConversionOutcome outcome = convert(input); + assertTrue(outcome.declared(), warnings(outcome)); + assertEquals(extract(input), extract(outcome.pdfBytes())); + assertEquals( + 0, + outcome.tagging().figuresNeedingAltText(), + "a form whose text is reachable must not degrade to an undescribed figure"); + } + + @Test + @DisplayName("a page built from multiple content streams converts as one sequence") + void multiStreamPageConverts() throws Exception { + byte[] input = PdfUaTestDocuments.multiStreamDocument(); + PdfUaConversionOutcome outcome = convert(input); + assertTrue(outcome.declared(), warnings(outcome)); + assertEquals(extract(input), extract(outcome.pdfBytes())); + } + + @Test + @DisplayName("a rotated page keeps its text and converts") + void rotatedPageConverts() throws Exception { + byte[] input = PdfUaTestDocuments.rotatedDocument(); + PdfUaConversionOutcome outcome = convert(input); + assertEquals(extract(input), extract(outcome.pdfBytes())); + assertTrue(outcome.tagging().taggedElements() > 0, "rotated text was not tagged"); + } + + @Test + @DisplayName("a MediaBox that does not start at the origin does not break analysis") + void offsetMediaBoxConverts() throws Exception { + byte[] input = PdfUaTestDocuments.offsetMediaBoxDocument(); + PdfUaConversionOutcome outcome = convert(input); + assertTrue(outcome.declared(), warnings(outcome)); + assertEquals(extract(input), extract(outcome.pdfBytes())); + } + + private static String warnings(PdfUaConversionOutcome outcome) { + return "warnings: " + + String.join(" | ", outcome.warnings()) + + " issues: " + + outcome.validation().issues(); + } + } + + @Nested + @DisplayName("pre-marked content") + class PreMarked { + + @Test + @DisplayName("existing BDC/EMC operators are stripped before new ones are written") + void stripsExistingMarkedContent() throws Exception { + byte[] premarked = premarkedDocument(); + PdfUaConversionOutcome outcome = convert(premarked); + + try (PDDocument document = Loader.loadPDF(outcome.pdfBytes())) { + Counts counts = countMarkedContent(document.getPage(0)); + assertEquals( + counts.opens(), + counts.closes(), + "unbalanced marked content after stripping and re-injection"); + assertFalse( + contentOf(document).contains("/OldTag"), + "the source's own marked content survived the rebuild"); + } + assertEquals(extract(premarked), extract(outcome.pdfBytes())); + } + + /** A document whose stream already contains a BDC sequence under a custom tag. */ + private static byte[] premarkedDocument() throws Exception { + byte[] plain = PdfUaTestDocuments.simpleDocument(); + try (PDDocument document = Loader.loadPDF(plain)) { + PDPage page = document.getPage(0); + String content; + try (InputStream in = page.getContents()) { + content = new String(in.readAllBytes(), StandardCharsets.ISO_8859_1); + } + String wrapped = "/OldTag <> BDC\n" + content + "\nEMC\n"; + var stream = new org.apache.pdfbox.pdmodel.common.PDStream(document); + try (var out = stream.createOutputStream()) { + out.write(wrapped.getBytes(StandardCharsets.ISO_8859_1)); + } + page.setContents(stream); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + return out.toByteArray(); + } + } + + private record Counts(int opens, int closes) {} + + private static Counts countMarkedContent(PDPage page) throws IOException { + int opens = 0; + int closes = 0; + PDFStreamParser parser = new PDFStreamParser(page); + Object token; + while ((token = parser.parseNextToken()) != null) { + if (token instanceof Operator operator) { + switch (operator.getName()) { + case "BDC", "BMC" -> opens++; + case "EMC" -> closes++; + default -> {} + } + } + } + return new Counts(opens, closes); + } + + private static String contentOf(PDDocument document) throws IOException { + try (InputStream in = document.getPage(0).getContents()) { + return new String(in.readAllBytes(), StandardCharsets.ISO_8859_1); + } + } + } + + @Nested + @DisplayName("honesty rules") + class Honesty { + + @Test + @DisplayName("suppressed text blocks the conformance claim even when validation passes") + void suppressedTextBlocksDeclaration() { + var structure = new stirling.software.proprietary.pdf.ua.DocumentStructure(); + structure.setTextSuppressed(true); + var result = new stirling.software.proprietary.pdf.ua.TaggingResult(structure, true); + assertTrue( + result.isContentSuppressed(), + "the suppression flag must survive into the tagging result"); + } + + @Test + @DisplayName("dropped lines are reported per page by the analyser") + void analyserWarnsOnDroppedLines() { + PageContent dropped = + new PageContent( + 0, + List.of(), + List.of(), + 5, + false, + false, + true, + new stirling.software.proprietary.pdf.ua.BBox(0, 0, 595, 842)); + var structure = + new stirling.software.proprietary.pdf.ua.LayoutAnalyzer() + .analyse(List.of(dropped)); + assertTrue(structure.isTextSuppressed()); + assertTrue( + structure.getWarnings().stream().anyMatch(w -> w.contains("page(s) 1")), + "warning should name the affected page: " + structure.getWarnings()); + } + } + + @Nested + @DisplayName("clause table") + class Clauses { + + @Test + @DisplayName("subclauses resolve to their parent entry, not to a string prefix") + void subclauseLookupWalksSegments() { + assertNotNull(PdfUaValidationService.lookupClause("7.21.4.1"), "7.21.4.1 -> 7.21"); + assertNotNull(PdfUaValidationService.lookupClause("7.18.1"), "7.18.1 -> 7.18"); + var toUnicode = PdfUaValidationService.lookupClause("7.21.7"); + assertNotNull(toUnicode); + assertFalse( + toUnicode.autoFixable(), + "a missing ToUnicode map is not fixable by embedding fonts"); + assertNull(PdfUaValidationService.lookupClause("9.9.9")); + assertNull(PdfUaValidationService.lookupClause(null)); + } + } + + @Nested + @DisplayName("range claiming") + class Claiming { + + @Test + @DisplayName("a figure drawn between two text runs on one line stays a figure") + void interleavedFigureIsNotSwallowed() throws Exception { + // Words at ordinals 0 and 2 with an image at ordinal 1 between them. + var line = + new stirling.software.proprietary.pdf.ua.TextLineInfo( + 0, + "left right", + new stirling.software.proprietary.pdf.ua.BBox(50, 700, 400, 712), + 11, + false, + 0, + 2, + false, + List.of( + new stirling.software.proprietary.pdf.ua.WordInfo( + "left", + new stirling.software.proprietary.pdf.ua.BBox( + 50, 700, 90, 712), + 0, + 0, + 11, + false), + new stirling.software.proprietary.pdf.ua.WordInfo( + "right", + new stirling.software.proprietary.pdf.ua.BBox( + 360, 700, 400, 712), + 2, + 2, + 11, + false))); + var image = + new stirling.software.proprietary.pdf.ua.MarkableOp( + 1, + stirling.software.proprietary.pdf.ua.MarkableOp.Kind.IMAGE, + new stirling.software.proprietary.pdf.ua.BBox(150, 650, 350, 760), + "Im0"); + PageContent page = + new PageContent( + 0, + List.of(line), + List.of(image), + 3, + false, + false, + false, + new stirling.software.proprietary.pdf.ua.BBox(0, 0, 595, 842)); + + var structure = + new stirling.software.proprietary.pdf.ua.LayoutAnalyzer() + .analyse(List.of(page)); + List figures = new java.util.ArrayList<>(); + structure.visit( + block -> { + if (block.getType() == StructType.FIGURE) { + figures.add(block); + } + }); + assertEquals( + 1, + figures.size(), + "the image between the words must survive as its own figure"); + } + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaHttpEndpointTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaHttpEndpointTest.java new file mode 100644 index 0000000000..90c40af82b --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaHttpEndpointTest.java @@ -0,0 +1,183 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; +import stirling.software.common.util.TempFileManager; +import stirling.software.proprietary.controller.api.converters.ConvertPdfToPdfUa; +import stirling.software.proprietary.controller.api.security.AccessibilityReportController; + +/** + * Exercises the endpoints over HTTP, not through the service layer. Covers route mapping, multipart + * binding, and the headers and JSON a client depends on. + */ +class PdfUaHttpEndpointTest { + + private static MockMvc convertMvc; + private static MockMvc reportMvc; + private static final ObjectMapper JSON = new ObjectMapper(); + + @BeforeAll + static void setUp() throws Exception { + PdfUaValidationService validation = new PdfUaValidationService(); + validation.initialise(); + PdfUaConversionService conversion = + new PdfUaConversionService( + validation, + new FontEmbeddingService(), + new CustomPDFDocumentFactory( + org.mockito.Mockito.mock(PdfMetadataService.class))); + + // A real TempFileManager, so the streamed response path is exercised rather than mocked. + TempFileManager tempFiles = + new TempFileManager( + new stirling.software.common.util.TempFileRegistry(), + new stirling.software.common.model.ApplicationProperties()); + + // Stand in for the app's advice, which lives in core; without one every rejection is a 500. + var advice = new BadRequestAdvice(); + convertMvc = + MockMvcBuilders.standaloneSetup(new ConvertPdfToPdfUa(conversion, tempFiles)) + .setControllerAdvice(advice) + .build(); + reportMvc = + MockMvcBuilders.standaloneSetup( + new AccessibilityReportController( + new AccessibilityAuditService(validation))) + .setControllerAdvice(advice) + .build(); + } + + private static MockMultipartFile upload(byte[] pdf, String name) { + return new MockMultipartFile("fileInput", name, "application/pdf", pdf); + } + + /** Mirrors the one rule these endpoints rely on: a rejected input is a 400, not a 500. */ + @org.springframework.web.bind.annotation.RestControllerAdvice + static class BadRequestAdvice { + @org.springframework.web.bind.annotation.ExceptionHandler(IllegalArgumentException.class) + org.springframework.http.ResponseEntity badRequest(IllegalArgumentException ex) { + return org.springframework.http.ResponseEntity.badRequest().body(ex.getMessage()); + } + } + + @Test + @DisplayName("POST /api/v1/convert/pdf/ua returns a PDF and reports what it did in headers") + void conversionEndpointResponds() throws Exception { + MvcResult result = + convertMvc + .perform( + multipart("/api/v1/convert/pdf/ua") + .file(upload(PdfUaTestDocuments.simpleDocument(), "in.pdf")) + .param("language", "en-GB") + .param("title", "Over The Wire") + .param("embedFonts", "false")) + .andExpect(status().isOk()) + .andExpect(header().exists("X-Stirling-UA-Declared")) + .andExpect(header().exists("X-Stirling-UA-Failures")) + .andReturn(); + + byte[] body = result.getResponse().getContentAsByteArray(); + assertTrue(body.length > 0, "no document came back"); + assertEquals( + "%PDF", + new String(body, 0, 4, java.nio.charset.StandardCharsets.ISO_8859_1), + "the response body is not a PDF"); + assertEquals( + "true", + result.getResponse().getHeader("X-Stirling-UA-Declared"), + "a simple embedded-font document should convert and be declared conformant"); + } + + @Test + @DisplayName("POST /api/v1/security/accessibility-report returns the figure inventory as JSON") + void reportEndpointResponds() throws Exception { + MvcResult result = + reportMvc + .perform( + multipart("/api/v1/security/accessibility-report") + .file(upload(PdfUaTestDocuments.imageDocument(), "in.pdf")) + .param("profile", "ua1")) + .andExpect(status().isOk()) + .andReturn(); + + JsonNode json = JSON.readTree(result.getResponse().getContentAsString()); + assertEquals("PDF/UA-1", json.get("profile").asText()); + assertNotNull(json.get("summary"), "the report should carry a summary"); + + JsonNode figures = json.get("figuresNeedingDescription"); + assertNotNull(figures, "the field a caller needs to supply alt text is missing"); + assertTrue(figures.isArray() && figures.size() > 0, "the image should be listed: " + json); + assertTrue( + figures.get(0).get("key").asText().matches("\\d+:\\d+"), + "the key must be usable in a follow-up conversion request"); + } + + @Test + @DisplayName("alt text supplied as form data reaches the converter over HTTP") + void altTextBindsFromFormData() throws Exception { + byte[] input = PdfUaTestDocuments.imageDocument(); + + // Discover the key the way a client would, through the report endpoint. + MvcResult reported = + reportMvc + .perform( + multipart("/api/v1/security/accessibility-report") + .file(upload(input, "in.pdf"))) + .andExpect(status().isOk()) + .andReturn(); + String key = + JSON.readTree(reported.getResponse().getContentAsString()) + .get("figuresNeedingDescription") + .get(0) + .get("key") + .asText(); + + MvcResult converted = + convertMvc + .perform( + multipart("/api/v1/convert/pdf/ua") + .file(upload(input, "in.pdf")) + .param("embedFonts", "false") + .param("altText", key + "=A blue rectangle")) + .andExpect(status().isOk()) + .andReturn(); + + assertEquals( + "0", + converted.getResponse().getHeader("X-Stirling-UA-Figures-Needing-Alt"), + "the description posted as form data was not applied"); + } + + @Test + @DisplayName("a request with no file is rejected rather than processed") + void missingFileIsRejected() throws Exception { + convertMvc + .perform( + multipart("/api/v1/convert/pdf/ua") + .file( + new MockMultipartFile( + "fileInput", + "e.pdf", + "application/pdf", + new byte[0]))) + .andExpect(status().is4xxClientError()); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaRealCorpusTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaRealCorpusTest.java new file mode 100644 index 0000000000..9ebda08736 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaRealCorpusTest.java @@ -0,0 +1,249 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.TaggingOptions; + +/** + * Runs the converter over every PDF in the repository, where real-tool output breaks assumptions. A + * clean refusal counts as a pass; nothing may crash or make a false conformance claim. + */ +class PdfUaRealCorpusTest { + + private static PdfUaConversionService service; + private static Path repoRoot; + + /** Files the converter is expected to refuse rather than process. */ + private static final List EXPECTED_REJECTS = List.of("encrypted.pdf", "corrupted.pdf"); + + @BeforeAll + static void setUp() { + PdfUaValidationService validation = new PdfUaValidationService(); + validation.initialise(); + service = + new PdfUaConversionService( + validation, + new FontEmbeddingService(), + new CustomPDFDocumentFactory( + org.mockito.Mockito.mock(PdfMetadataService.class))); + repoRoot = findRepoRoot(); + } + + private static Path findRepoRoot() { + Path current = Path.of("").toAbsolutePath(); + while (current != null && !Files.exists(current.resolve("settings.gradle"))) { + current = current.getParent(); + } + return current; + } + + private record Outcome(String name, String status, int failures, int elements, int artifacts) {} + + private static TaggingOptions.TaggingOptionsBuilder options(String fallbackTitle) { + return TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .fallbackTitle(fallbackTitle) + .existingTags(TaggingOptions.ExistingTags.REBUILD); + } + + @Test + @DisplayName("converts every PDF in the repository without crashing or lying about conformance") + void realCorpusConverts() throws Exception { + assertNotNull(repoRoot, "could not locate the repository root"); + List pdfs = findPdfs(); + assertTrue(pdfs.size() >= 20, "expected a substantial corpus, found " + pdfs.size()); + + List outcomes = new ArrayList<>(); + List crashes = new ArrayList<>(); + java.util.Map clauseFiles = new java.util.TreeMap<>(); + java.util.Map clauseText = new java.util.HashMap<>(); + java.util.Map clauseExamples = new java.util.HashMap<>(); + + for (Path pdf : pdfs) { + String name = repoRoot.relativize(pdf).toString().replace('\\', '/'); + byte[] input; + try { + input = Files.readAllBytes(pdf); + } catch (IOException e) { + continue; + } + try { + String stem = pdf.getFileName().toString().replaceFirst("\\.pdf$", ""); + + // Fidelity is a tagging property, so measure it with font embedding off. + PdfUaConversionOutcome taggedOnly = + service.convert(input, options(stem).embedFonts(false).build()); + assertTextPreserved(name, input, taggedOnly.pdfBytes()); + + PdfUaConversionOutcome outcome = service.convert(input, options(stem).build()); + // Full pipeline too: Ghostscript can exit 0 having blanked the document. + assertTextPreserved(name + " (with font embedding)", input, outcome.pdfBytes()); + outcomes.add( + new Outcome( + name, + outcome.declared() ? "CONFORMS" : "improved", + outcome.validation().totalFailures(), + outcome.tagging().taggedElements(), + outcome.tagging().artifacts())); + outcome.validation() + .issues() + .forEach( + issue -> { + clauseFiles.merge(issue.getClause(), 1, Integer::sum); + clauseText.putIfAbsent( + issue.getClause(), issue.getTechnicalMessage()); + clauseExamples.putIfAbsent(issue.getClause(), name); + }); + + } catch (IOException e) { + // A refusal with an explanation is an acceptable outcome. + outcomes.add(new Outcome(name, "refused: " + e.getMessage(), 0, 0, 0)); + } catch (RuntimeException e) { + crashes.add(name + " -> " + e); + outcomes.add(new Outcome(name, "CRASH: " + e, 0, 0, 0)); + } + } + + System.out.println(render(outcomes)); + + StringBuilder clauses = + new StringBuilder("\nBlocking clauses, by number of files affected\n"); + clauseFiles.entrySet().stream() + .sorted(java.util.Map.Entry.comparingByValue().reversed()) + .forEach( + e -> + clauses.append( + String.format( + " clause %-9s %-3d files e.g. %s%n %s%n", + e.getKey(), + e.getValue(), + clauseExamples.get(e.getKey()), + abbreviate(clauseText.get(e.getKey()))))); + System.out.println(clauses); + + List unexpectedCrashes = + crashes.stream() + .filter(c -> EXPECTED_REJECTS.stream().noneMatch(c::contains)) + .toList(); + assertTrue( + unexpectedCrashes.isEmpty(), + "converter crashed on: " + String.join("; ", unexpectedCrashes)); + } + + /** Tagging must not change extracted text; a diff means the rewrite corrupted the page. */ + private static void assertTextPreserved(String name, byte[] before, byte[] after) { + String textBefore = safeExtract(before); + if (textBefore == null) { + // The source itself is unreadable, so there is nothing to compare against. + return; + } + String textAfter = safeExtract(after); + assertTrue( + textAfter != null, "the converted file could not be read back at all for " + name); + assertTrue( + textBefore.equals(textAfter), + "tagging changed extracted text for " + + name + + "\n before: " + + preview(textBefore) + + "\n after: " + + preview(textAfter)); + } + + private static String abbreviate(String text) { + if (text == null) { + return ""; + } + return text.length() <= 110 ? text : text.substring(0, 110) + "..."; + } + + private static String preview(String text) { + return text.length() <= 160 ? text : text.substring(0, 160) + "..."; + } + + private static String safeExtract(byte[] pdf) { + try (PDDocument document = Loader.loadPDF(pdf)) { + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setSortByPosition(true); + return normalise(stripper.getText(document)); + } catch (Exception e) { + return null; + } + } + + /** Drops invisible formatting characters: a rebuild legitimately loses soft hyphens. */ + static String normalise(String text) { + StringBuilder sb = new StringBuilder(text.length()); + text.codePoints() + .forEach( + cp -> { + if (Character.getType(cp) != Character.FORMAT && cp != 0x00AD) { + sb.appendCodePoint(cp); + } + }); + return sb.toString().replaceAll("\\s+", " ").strip(); + } + + private List findPdfs() throws IOException { + try (Stream stream = Files.walk(repoRoot)) { + return stream.filter(Files::isRegularFile) + .filter(p -> p.toString().toLowerCase().endsWith(".pdf")) + .filter(p -> !p.toString().contains("node_modules")) + .filter(p -> !p.toString().contains(File_BUILD)) + .filter(p -> !p.toString().contains(".git")) + .sorted(Comparator.comparing(Path::toString)) + .toList(); + } + } + + private static final String File_BUILD = "build" + java.io.File.separator; + + private static String render(List outcomes) { + StringBuilder sb = new StringBuilder("\nPDF/UA conversion over the repository corpus\n"); + long conforming = outcomes.stream().filter(o -> "CONFORMS".equals(o.status())).count(); + long refused = outcomes.stream().filter(o -> o.status().startsWith("refused")).count(); + sb.append( + String.format( + " %d files: %d conform, %d improved but not conformant, %d refused%n%n", + outcomes.size(), + conforming, + outcomes.size() - conforming - refused, + refused)); + for (Outcome outcome : outcomes) { + sb.append( + String.format( + " %-62s %-10s fail=%-4d el=%-5d art=%d%n", + outcome.name().length() > 60 + ? "..." + outcome.name().substring(outcome.name().length() - 57) + : outcome.name(), + outcome.status().length() > 10 + ? outcome.status().substring(0, 10) + : outcome.status(), + outcome.failures(), + outcome.elements(), + outcome.artifacts())); + } + return sb.toString(); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaSampleDumpTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaSampleDumpTest.java new file mode 100644 index 0000000000..46cdb37c76 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaSampleDumpTest.java @@ -0,0 +1,103 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.TaggingOptions; + +/** + * Dumps converted output for independent checkers; validating PDFBox with PDFBox is circular. Off + * by default as it writes outside the build directory: run with {@code DUMP_UA_SAMPLES=}. + */ +@EnabledIfEnvironmentVariable(named = "DUMP_UA_SAMPLES", matches = ".+") +class PdfUaSampleDumpTest { + + private static PdfUaConversionService service; + private static Path repoRoot; + + @BeforeAll + static void setUp() { + PdfUaValidationService validation = new PdfUaValidationService(); + validation.initialise(); + service = + new PdfUaConversionService( + validation, + new FontEmbeddingService(), + new CustomPDFDocumentFactory( + org.mockito.Mockito.mock(PdfMetadataService.class))); + repoRoot = Path.of("").toAbsolutePath(); + while (repoRoot != null && !Files.exists(repoRoot.resolve("settings.gradle"))) { + repoRoot = repoRoot.getParent(); + } + } + + @Test + @DisplayName("writes original and converted pairs for external validation") + void dumpSamples() throws Exception { + Path out = Path.of(System.getenv("DUMP_UA_SAMPLES")); + Files.createDirectories(out); + + List pdfs; + try (Stream stream = Files.walk(repoRoot)) { + pdfs = + stream.filter(Files::isRegularFile) + .filter(p -> p.toString().toLowerCase().endsWith(".pdf")) + .filter(p -> !p.toString().contains("node_modules")) + .filter(p -> !p.toString().contains(java.io.File.separator + "build")) + .filter(p -> !p.toString().contains(".git")) + .sorted() + .toList(); + } + + List manifest = new ArrayList<>(); + int written = 0; + for (Path pdf : pdfs) { + String stem = pdf.getFileName().toString().replaceFirst("\\.pdf$", ""); + byte[] input; + try { + input = Files.readAllBytes(pdf); + } catch (Exception e) { + continue; + } + try { + PdfUaConversionOutcome outcome = + service.convert( + input, + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .fallbackTitle(stem) + .existingTags(TaggingOptions.ExistingTags.REBUILD) + .build()); + Files.write(out.resolve(stem + "__before.pdf"), input); + Files.write(out.resolve(stem + "__after.pdf"), outcome.pdfBytes()); + manifest.add( + stem + + "\tdeclared=" + + outcome.declared() + + "\tfailures=" + + outcome.validation().totalFailures()); + written++; + } catch (Exception e) { + manifest.add(stem + "\tREFUSED\t" + e.getMessage()); + } + } + Files.write(out.resolve("manifest.tsv"), manifest); + System.out.println("Wrote " + written + " before/after pairs to " + out); + assertTrue(written > 10, "expected a usable sample set, wrote " + written); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaServicesTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaServicesTest.java new file mode 100644 index 0000000000..9d2e14ee0d --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaServicesTest.java @@ -0,0 +1,319 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +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 java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.encryption.AccessPermission; +import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.font.Standard14Fonts; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; +import stirling.software.proprietary.model.api.ua.AccessibilityReport; +import stirling.software.proprietary.model.api.ua.PdfUaConversionOutcome; +import stirling.software.proprietary.model.api.ua.UaValidationResult; +import stirling.software.proprietary.pdf.ua.PdfUaProfile; +import stirling.software.proprietary.pdf.ua.TaggingOptions; + +/** Tests for the services that validate, audit and convert. */ +class PdfUaServicesTest { + + private static PdfUaValidationService validation; + private static PdfUaConversionService conversion; + private static AccessibilityAuditService audit; + private static FontEmbeddingService fonts; + + @BeforeAll + static void setUp() { + validation = new PdfUaValidationService(); + validation.initialise(); + fonts = new FontEmbeddingService(); + conversion = + new PdfUaConversionService( + validation, + fonts, + new CustomPDFDocumentFactory( + org.mockito.Mockito.mock(PdfMetadataService.class))); + audit = new AccessibilityAuditService(validation); + } + + /** Uses a standard 14 font deliberately: never embedded, which clause 7.21 forbids. */ + private static byte[] unembeddedFontPdf() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + cs.beginText(); + cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); + cs.newLineAtOffset(50, 700); + cs.showText("Hello accessibility"); + cs.endText(); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + return out.toByteArray(); + } + } + + private static byte[] manyPages(int pages) throws IOException { + try (PDDocument document = new PDDocument()) { + for (int i = 0; i < pages; i++) { + document.addPage(new PDPage()); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + return out.toByteArray(); + } + } + + private static byte[] encryptedPdf() throws IOException { + try (PDDocument document = new PDDocument()) { + document.addPage(new PDPage()); + AccessPermission permissions = new AccessPermission(); + document.protect(new StandardProtectionPolicy("owner", "user", permissions)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + return out.toByteArray(); + } + } + + @Nested + @DisplayName("validation") + class Validation { + + @Test + @DisplayName("an untagged document fails and the failures are grouped by rule") + void untaggedFails() throws Exception { + UaValidationResult result = validation.validate(unembeddedFontPdf(), PdfUaProfile.UA1); + assertFalse(result.compliant()); + assertTrue(result.hasIssues()); + assertTrue( + result.totalFailures() >= result.issues().size(), + "grouping must not invent failures"); + assertTrue( + result.issues().stream().allMatch(i -> i.getOccurrences() > 0), + "every grouped issue should count its occurrences"); + } + + @Test + @DisplayName("malformed input reports a failure instead of throwing") + void malformedInputIsReported() { + UaValidationResult result = + validation.validate("not a pdf".getBytes(), PdfUaProfile.UA1); + assertFalse(result.compliant()); + assertFalse(result.issues().isEmpty()); + } + + @Test + @DisplayName("issues carry plain-English text as well as the validator's own wording") + void issuesAreReadable() throws Exception { + UaValidationResult result = validation.validate(unembeddedFontPdf(), PdfUaProfile.UA1); + assertTrue( + result.issues().stream() + .allMatch(i -> i.getMessage() != null && !i.getMessage().isBlank())); + } + } + + @Nested + @DisplayName("auditing") + class Auditing { + + @Test + @DisplayName("reports the document facts that drive most failures") + void reportsSummary() throws Exception { + AccessibilityReport report = audit.audit(unembeddedFontPdf(), PdfUaProfile.UA1); + + assertFalse(report.isTagged(), "the fixture has no structure tree"); + assertFalse(report.isDeclaresConformance()); + assertFalse(report.isPassesAutomatedChecks()); + assertEquals(1, report.getSummary().getPages()); + assertFalse(report.getSummary().isAllFontsEmbedded()); + assertTrue(report.getSummary().getUnembeddedFonts() > 0); + assertFalse(report.getSummary().isHasLanguage()); + assertFalse(report.getSummary().isHasTitle()); + } + + @Test + @DisplayName("always lists the checks a person still has to make") + void listsHumanChecks() throws Exception { + AccessibilityReport report = audit.audit(unembeddedFontPdf(), PdfUaProfile.UA1); + assertFalse( + report.getHumanChecks().isEmpty(), + "a report showing only automated results implies the rest does not exist"); + } + + @Test + @DisplayName("splits failures into automatically fixable and needs-input") + void splitsRemediability() throws Exception { + AccessibilityReport report = audit.audit(unembeddedFontPdf(), PdfUaProfile.UA1); + assertEquals( + report.getIssues().size(), + report.getAutomaticallyFixable() + report.getNeedsInput()); + } + + @Test + @DisplayName("refuses a document past the page cap the conversion also applies") + void refusesTooManyPages() throws Exception { + byte[] oversized = manyPages(2001); + assertThrows( + IllegalArgumentException.class, + () -> audit.audit(oversized, PdfUaProfile.UA1), + "an uncapped report walks every page of any document a caller uploads"); + } + + @Test + @DisplayName("a converted document reports as tagged and conformant") + void reportsAfterConversion() throws Exception { + PdfUaConversionOutcome outcome = + conversion.convert( + unembeddedFontPdf(), + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("Converted") + .build()); + AccessibilityReport report = audit.audit(outcome.pdfBytes(), PdfUaProfile.UA1); + assertTrue(report.isTagged()); + assertTrue(report.getSummary().isHasTitle()); + assertTrue(report.getSummary().isHasLanguage()); + assertTrue(report.getSummary().isDisplaysDocTitle()); + } + } + + @Nested + @DisplayName("font embedding") + class Fonts { + + @Test + @DisplayName("detects a standard 14 font as unembedded") + void detectsUnembedded() throws Exception { + assertTrue(fonts.hasUnembeddedFonts(unembeddedFontPdf())); + } + + @Test + @DisplayName("embeds fonts, or explains why it could not") + void embedsOrExplains() throws Exception { + FontEmbeddingService.Result result = fonts.embedFonts(unembeddedFontPdf()); + assertNotNull(result.pdfBytes()); + if (result.changed()) { + assertFalse( + fonts.hasUnembeddedFonts(result.pdfBytes()), + "embedding reported success but fonts are still missing"); + } else { + assertNotNull( + result.warning(), "failing to embed must be explained, not passed over"); + } + } + + @Test + @DisplayName("leaves a document alone when every font is already embedded") + void skipsWhenNothingToDo() throws Exception { + byte[] embedded = PdfUaTestDocuments.simpleDocument(); + FontEmbeddingService.Result result = fonts.embedFonts(embedded); + assertFalse(result.changed()); + assertEquals(embedded.length, result.pdfBytes().length); + } + } + + @Nested + @DisplayName("conversion") + class Conversion { + + @Test + @DisplayName("refuses an encrypted document with an explanation") + void refusesEncrypted() throws Exception { + byte[] encrypted = encryptedPdf(); + IOException error = + assertThrows( + IOException.class, + () -> + conversion.convert( + encrypted, + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .build())); + assertTrue(error.getMessage().toLowerCase().contains("encrypted")); + } + + @Test + @DisplayName("keeping existing tags does not rebuild the tree") + void keepRespectsExistingTags() throws Exception { + byte[] tagged = + conversion + .convert( + PdfUaTestDocuments.simpleDocument(), + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("First pass") + .embedFonts(false) + .build()) + .pdfBytes(); + + PdfUaConversionOutcome second = + conversion.convert( + tagged, + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("Second pass") + .embedFonts(false) + .existingTags(TaggingOptions.ExistingTags.KEEP) + .build()); + + assertFalse(second.tagging().rebuiltStructure(), "KEEP must not rebuild"); + try (PDDocument document = Loader.loadPDF(second.pdfBytes())) { + assertEquals("Second pass", document.getDocumentInformation().getTitle()); + } + } + + @Test + @DisplayName("marking images decorative removes the alt-text blocker") + void decorativePolicyClearsFigures() throws Exception { + PdfUaConversionOutcome outcome = + conversion.convert( + PdfUaTestDocuments.imageDocument(), + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("Decorative") + .embedFonts(false) + .figurePolicy(TaggingOptions.FigurePolicy.MARK_DECORATIVE) + .build()); + assertEquals(0, outcome.tagging().figuresNeedingAltText()); + assertTrue(outcome.declared(), "with no undescribed figures the file should conform"); + } + + @Test + @DisplayName("converting twice produces the same conformance verdict") + void conversionIsStable() throws Exception { + byte[] input = PdfUaTestDocuments.headingHierarchy(); + TaggingOptions options = + TaggingOptions.builder() + .profile(PdfUaProfile.UA1) + .language("en-GB") + .title("Stable") + .embedFonts(false) + .build(); + PdfUaConversionOutcome first = conversion.convert(input, options); + PdfUaConversionOutcome second = conversion.convert(first.pdfBytes(), options); + assertEquals(first.declared(), second.declared()); + } + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaTestDocuments.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaTestDocuments.java new file mode 100644 index 0000000000..589edbc1a2 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfUaTestDocuments.java @@ -0,0 +1,390 @@ +package stirling.software.proprietary.service.ua; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDFormContentStream; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType0Font; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.util.Matrix; + +/** + * Builds the fixture corpus used by the PDF/UA tests. Fonts are embedded deliberately: the standard + * 14 fail clause 7.21 and would mask every result. + */ +final class PdfUaTestDocuments { + + private static final String FONT_RESOURCE = "/static/fonts/DejaVuSans.ttf"; + // The font ships with core's resources, which are not on this module's classpath. + private static final String FONT_REPO_PATH = + "app/core/src/main/resources/static/fonts/DejaVuSans.ttf"; + private static final float MARGIN = 60f; + + private PdfUaTestDocuments() {} + + static PDFont font(PDDocument document) throws IOException { + try (InputStream in = PdfUaTestDocuments.class.getResourceAsStream(FONT_RESOURCE)) { + if (in != null) { + return PDType0Font.load(document, in, true); + } + } + Path repoRoot = Path.of("").toAbsolutePath(); + while (repoRoot != null && !Files.exists(repoRoot.resolve("settings.gradle"))) { + repoRoot = repoRoot.getParent(); + } + Path font = repoRoot == null ? null : repoRoot.resolve(FONT_REPO_PATH); + if (font == null || !Files.exists(font)) { + throw new IOException( + "Test font not found: " + FONT_RESOURCE + " or " + FONT_REPO_PATH); + } + try (InputStream in = Files.newInputStream(font)) { + return PDType0Font.load(document, in, true); + } + } + + /** A heading followed by two paragraphs: the simplest thing that should convert cleanly. */ + static byte[] simpleDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + float y = 760; + y = text(cs, font, 20, MARGIN, y, "Quarterly Report"); + y -= 14; + y = text(cs, font, 11, MARGIN, y, "This document summarises the results for the"); + y = text(cs, font, 11, MARGIN, y, "period and outlines the outlook for next year."); + y -= 14; + text(cs, font, 11, MARGIN, y, "A second paragraph follows the first one here."); + } + return bytes(document); + } + } + + /** Three heading tiers, to exercise level assignment and the no-skipped-levels rule. */ + static byte[] headingHierarchy() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + float y = 760; + y = text(cs, font, 24, MARGIN, y, "Annual Review"); + y -= 12; + y = text(cs, font, 11, MARGIN, y, "Introductory prose sits under the title here."); + y -= 16; + y = text(cs, font, 17, MARGIN, y, "Financial Results"); + y -= 10; + y = text(cs, font, 11, MARGIN, y, "Revenue grew steadily across every region."); + y -= 16; + y = text(cs, font, 13, MARGIN, y, "Europe"); + y -= 10; + text(cs, font, 11, MARGIN, y, "European revenue rose by eleven per cent."); + } + return bytes(document); + } + } + + /** A bulleted and a numbered list. */ + static byte[] listDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + float y = 760; + y = text(cs, font, 18, MARGIN, y, "Checklist"); + y -= 14; + y = text(cs, font, 11, MARGIN, y, "• Review the source document"); + y = text(cs, font, 11, MARGIN, y, "• Check every heading level"); + y = text(cs, font, 11, MARGIN, y, "• Describe each image"); + y -= 16; + y = text(cs, font, 11, MARGIN, y, "1. Open the file"); + y = text(cs, font, 11, MARGIN, y, "2. Run the converter"); + text(cs, font, 11, MARGIN, y, "3. Validate the result"); + } + return bytes(document); + } + } + + /** A three-column table whose cells each occupy their own text-showing operator. */ + static byte[] tableDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + float y = 760; + y = text(cs, font, 18, MARGIN, y, "Regional Totals"); + y -= 20; + String[][] rows = { + {"Region", "Units", "Revenue"}, + {"North", "1200", "48000"}, + {"South", "980", "39200"}, + {"East", "1430", "57200"} + }; + float[] columns = {MARGIN, MARGIN + 160, MARGIN + 300}; + for (String[] row : rows) { + tableRow(cs, font, 11, columns, y, row); + y -= 20; + } + } + return bytes(document); + } + } + + /** A page with a real image, which must end up as a Figure needing alternative text. */ + static byte[] imageDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + BufferedImage bitmap = new BufferedImage(120, 90, BufferedImage.TYPE_INT_RGB); + java.awt.Graphics2D graphics = bitmap.createGraphics(); + graphics.setColor(Color.BLUE); + graphics.fillRect(0, 0, 120, 90); + graphics.dispose(); + PDImageXObject image = LosslessFactory.createFromImage(document, bitmap); + + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + float y = 760; + y = text(cs, font, 18, MARGIN, y, "Illustrated Page"); + y -= 20; + y = text(cs, font, 11, MARGIN, y, "The chart below shows the trend."); + cs.drawImage(image, MARGIN, y - 120, 180, 100); + } + return bytes(document); + } + } + + /** Four pages sharing a running head and a page number, which must become artifacts. */ + static byte[] runningHeadersDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDFont font = null; + for (int i = 1; i <= 4; i++) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + if (font == null) { + font = font(document); + } + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + text(cs, font, 9, MARGIN, 810, "Confidential Internal Report"); + float y = 750; + y = text(cs, font, 16, MARGIN, y, "Section " + i); + y -= 12; + text(cs, font, 11, MARGIN, y, "Body text for section number " + i + " here."); + text(cs, font, 9, 300, 30, "Page " + i); + } + } + return bytes(document); + } + } + + /** Two columns of prose, to exercise reading order. */ + static byte[] twoColumnDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + float left = MARGIN; + float right = 320; + float y = 740; + for (int i = 1; i <= 8; i++) { + text(cs, font, 10, left, y - i * 16, "Left column line number " + i); + text(cs, font, 10, right, y - i * 16, "Right column line number " + i); + } + } + return bytes(document); + } + } + + /** A page carrying a link annotation, which must be reachable from the structure tree. */ + static byte[] linkDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + float y = 760; + y = text(cs, font, 18, MARGIN, y, "Useful Links"); + text(cs, font, 11, MARGIN, y - 20, "Visit the project home page for details."); + } + PDAnnotationLink link = new PDAnnotationLink(); + PDRectangle rectangle = new PDRectangle(); + rectangle.setLowerLeftX(MARGIN); + rectangle.setLowerLeftY(725); + rectangle.setUpperRightX(MARGIN + 200); + rectangle.setUpperRightY(740); + link.setRectangle(rectangle); + PDActionURI action = new PDActionURI(); + action.setURI("https://example.org"); + link.setAction(action); + page.getAnnotations().add(link); + return bytes(document); + } + } + + /** A page with no content at all. */ + static byte[] emptyDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + document.addPage(new PDPage(PDRectangle.A4)); + return bytes(document); + } + } + + /** A page whose only content is a full-page image, standing in for an un-OCRed scan. */ + static byte[] scannedDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + BufferedImage bitmap = new BufferedImage(600, 850, BufferedImage.TYPE_INT_RGB); + java.awt.Graphics2D graphics = bitmap.createGraphics(); + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, 600, 850); + graphics.setColor(Color.BLACK); + graphics.drawString("scanned page", 40, 60); + graphics.dispose(); + PDImageXObject image = LosslessFactory.createFromImage(document, bitmap); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + cs.drawImage(image, 0, 0, PDRectangle.A4.getWidth(), PDRectangle.A4.getHeight()); + } + return bytes(document); + } + } + + /** Text drawn inside a form XObject, attributed to the Do operator that invoked it. */ + static byte[] formXObjectDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + + PDFormXObject form = new PDFormXObject(document); + form.setBBox(new PDRectangle(220, 40)); + form.setResources(new PDResources()); + try (PDFormContentStream fcs = new PDFormContentStream(form)) { + fcs.beginText(); + fcs.setFont(font, 11); + fcs.newLineAtOffset(4, 14); + fcs.showText("Text living inside the form XObject"); + fcs.endText(); + } + + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + text(cs, font, 18, MARGIN, 760, "Page With Embedded Form"); + text(cs, font, 11, MARGIN, 730, "Ordinary page text sits above the form."); + cs.saveGraphicsState(); + cs.transform(Matrix.getTranslateInstance(MARGIN, 650)); + cs.drawForm(form); + cs.restoreGraphicsState(); + } + return bytes(document); + } + } + + /** Content split across two streams (a PDF array) - the parser must see one sequence. */ + static byte[] multiStreamDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + text(cs, font, 18, MARGIN, 760, "First Stream Heading"); + } + try (PDPageContentStream cs = + new PDPageContentStream( + document, page, PDPageContentStream.AppendMode.APPEND, true)) { + text(cs, font, 11, MARGIN, 720, "Second stream paragraph appended later."); + } + return bytes(document); + } + } + + /** A landscape page via /Rotate 90, which flips the frame the text engine reports in. */ + static byte[] rotatedDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(PDRectangle.A4); + page.setRotation(90); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + // Drawn rotated so the text reads upright on the rotated page. + cs.transform(Matrix.getRotateInstance(Math.toRadians(90), 595, 0)); + text(cs, font, 18, MARGIN, 500, "Rotated Page Title"); + text(cs, font, 11, MARGIN, 470, "Body text on a landscape page."); + } + return bytes(document); + } + } + + /** A MediaBox whose origin is not (0,0), which some scanners produce. */ + static byte[] offsetMediaBoxDocument() throws IOException { + try (PDDocument document = new PDDocument()) { + PDPage page = new PDPage(new PDRectangle(100, 200, 595, 842)); + document.addPage(page); + PDFont font = font(document); + try (PDPageContentStream cs = new PDPageContentStream(document, page)) { + text(cs, font, 18, 160, 960, "Offset Origin Title"); + text(cs, font, 11, 160, 930, "Text on a page whose MediaBox starts at 100,200."); + } + return bytes(document); + } + } + + // --- helpers ----------------------------------------------------------- + + private static float text( + PDPageContentStream cs, PDFont font, float size, float x, float y, String value) + throws IOException { + cs.beginText(); + cs.setFont(font, size); + cs.newLineAtOffset(x, y); + cs.showText(value); + cs.endText(); + return y - size * 1.35f; + } + + /** + * Emits one row with a separate show-text operator per cell, so each cell gets its own MCID. + */ + private static void tableRow( + PDPageContentStream cs, + PDFont font, + float size, + float[] columns, + float y, + String[] values) + throws IOException { + cs.beginText(); + cs.setFont(font, size); + cs.newLineAtOffset(columns[0], y); + cs.showText(values[0]); + for (int i = 1; i < values.length; i++) { + cs.newLineAtOffset(columns[i] - columns[i - 1], 0); + cs.showText(values[i]); + } + cs.endText(); + } + + private static byte[] bytes(PDDocument document) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + document.save(out); + return out.toByteArray(); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfaLevelATest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfaLevelATest.java new file mode 100644 index 0000000000..367b9d1171 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/PdfaLevelATest.java @@ -0,0 +1,202 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.verapdf.pdfa.Foundries; +import org.verapdf.pdfa.PDFAParser; +import org.verapdf.pdfa.flavours.PDFAFlavour; +import org.verapdf.pdfa.results.TestAssertion; +import org.verapdf.pdfa.results.ValidationResult; + +/** + * Proves tagging raises a PDF/A file from level B to the accessible level A. veraPDF is the + * arbiter: the claim only counts if the validator agrees. + */ +class PdfaLevelATest { + + private static PdfaAccessibilityService service; + private static Path repoRoot; + + @BeforeAll + static void setUp() { + PdfUaValidationService uaValidation = new PdfUaValidationService(); + uaValidation.initialise(); + service = new PdfaAccessibilityService(uaValidation); + repoRoot = Path.of("").toAbsolutePath(); + while (repoRoot != null && !Files.exists(repoRoot.resolve("settings.gradle"))) { + repoRoot = repoRoot.getParent(); + } + } + + private static byte[] fixture(String name) throws Exception { + return Files.readAllBytes( + repoRoot.resolve("app/core/src/test/resources/pdfa").resolve(name)); + } + + private static String xmpOf(byte[] pdf) throws Exception { + try (PDDocument document = Loader.loadPDF(pdf)) { + var metadata = document.getDocumentCatalog().getMetadata(); + assertNotNull(metadata, "no XMP packet"); + return new String(metadata.toByteArray(), StandardCharsets.UTF_8); + } + } + + /** The flavour the file declares in its XMP, which is what a validator picks up by itself. */ + private static String declaredStandard(byte[] pdf) throws Exception { + try (PDFAParser parser = + Foundries.defaultInstance().createParser(new ByteArrayInputStream(pdf))) { + List flavours = parser.getFlavours(); + return flavours == null || flavours.isEmpty() ? null : flavours.get(0).getId(); + } + } + + private static ValidationResult validate(byte[] pdf, PDFAFlavour flavour) throws Exception { + try (PDFAParser parser = + Foundries.defaultInstance().createParser(new ByteArrayInputStream(pdf), flavour)) { + return Foundries.defaultInstance().createValidator(flavour, false).validate(parser); + } + } + + private static List failures(ValidationResult result) { + return result.getTestAssertions().stream() + .filter(assertion -> assertion.getStatus() == TestAssertion.Status.FAILED) + .map(TestAssertion::getMessage) + .toList(); + } + + @Test + @DisplayName("a level B file gains a structure tree and a conformance A claim") + void upgradesLevelBToLevelA() throws Exception { + byte[] levelB = fixture("valid-pdfa-2b.pdf"); + + try (PDDocument before = Loader.loadPDF(levelB)) { + assertEquals( + null, + before.getDocumentCatalog().getStructureTreeRoot(), + "the fixture should start untagged, or the test proves nothing"); + } + + PdfaAccessibilityService.Result result = + service.upgradeToLevelA(levelB, 2, "en-GB", "Archived Report"); + assertTrue(result.levelA(), "upgrade failed: " + result.warnings()); + + try (PDDocument after = Loader.loadPDF(result.pdfBytes())) { + assertNotNull( + after.getDocumentCatalog().getStructureTreeRoot(), "no structure tree written"); + assertTrue(after.getDocumentCatalog().getMarkInfo().isMarked()); + assertEquals("en-GB", after.getDocumentCatalog().getLanguage()); + } + + String xmp = xmpOf(result.pdfBytes()); + assertTrue(xmp.contains("part"), "pdfaid:part missing"); + assertTrue( + xmp.contains(">A<") || xmp.contains("conformance=\"A\""), + "conformance was not raised to A: " + xmp); + } + + @Test + @DisplayName("the upgraded file still validates as PDF/A, now at level A") + void upgradedFileStillValidates() throws Exception { + byte[] levelB = fixture("valid-pdfa-2b.pdf"); + PdfaAccessibilityService.Result result = + service.upgradeToLevelA(levelB, 2, "en-GB", "Archived Report"); + assertTrue(result.levelA(), "upgrade failed: " + result.warnings()); + + assertEquals( + "2a", declaredStandard(result.pdfBytes()), "the file should now declare PDF/A-2a"); + + ValidationResult pdfa = validate(result.pdfBytes(), PDFAFlavour.PDFA_2_A); + assertTrue(pdfa.isCompliant(), () -> "PDF/A-2a validation failed: " + failures(pdfa)); + } + + @Test + @DisplayName("PDF/A-1 keeps its 1.4 version, since level A must not change the part") + void partOneKeepsItsVersion() throws Exception { + byte[] levelB = fixture("valid-pdfa-1b.pdf"); + float versionBefore; + try (PDDocument document = Loader.loadPDF(levelB)) { + versionBefore = document.getVersion(); + } + + PdfaAccessibilityService.Result result = + service.upgradeToLevelA(levelB, 1, "en", "Archived"); + try (PDDocument document = Loader.loadPDF(result.pdfBytes())) { + assertEquals( + versionBefore, + document.getVersion(), + "raising the PDF version would break PDF/A-1 conformance"); + } + } + + @Test + @DisplayName("a document with nothing to tag is left at level B rather than mislabelled") + void refusesToClaimLevelAWithoutTags() throws Exception { + byte[] blank; + try (PDDocument document = new PDDocument()) { + document.addPage(new org.apache.pdfbox.pdmodel.PDPage()); + var out = new java.io.ByteArrayOutputStream(); + document.save(out); + blank = out.toByteArray(); + } + + PdfaAccessibilityService.Result result = service.upgradeToLevelA(blank, 2, "en", "Empty"); + assertFalse(result.levelA(), "an untaggable document must not claim level A"); + assertFalse(result.warnings().isEmpty(), "the refusal should be explained"); + } + + @Test + @DisplayName("setting conformance leaves the rest of the XMP packet intact") + void conformanceRewritePreservesPacket() throws Exception { + byte[] levelB = fixture("valid-pdfa-2b.pdf"); + byte[] rewritten = PdfaAccessibilityService.setConformance(levelB, 2, "A"); + + assertEquals("2a", declaredStandard(rewritten), "the rewritten packet should declare 2a"); + } + + @Test + @DisplayName("a file can declare PDF/A and PDF/UA at once without breaking either") + void combinedPdfaAndPdfUa() throws Exception { + byte[] levelB = fixture("valid-pdfa-2b.pdf"); + PdfaAccessibilityService.Result upgraded = + service.upgradeToLevelA(levelB, 2, "en-GB", "Archived and Accessible"); + assertTrue(upgraded.levelA(), "upgrade failed: " + upgraded.warnings()); + + byte[] both = PdfaAccessibilityService.declarePdfUaAlongsidePdfa(upgraded.pdfBytes(), 2); + + String xmp = xmpOf(both); + assertTrue(xmp.contains("pdfuaid"), "no PDF/UA identifier"); + assertTrue( + xmp.contains("pdfaSchema") || xmp.contains("schemas"), + "PDF/A requires an extension schema describing pdfuaid, none found: " + xmp); + + assertEquals( + "2a", declaredStandard(both), "the combined file should still declare PDF/A-2a"); + + ValidationResult pdfa = validate(both, PDFAFlavour.PDFA_2_A); + assertTrue( + pdfa.isCompliant(), + () -> "adding the PDF/UA identifier broke PDF/A: " + failures(pdfa)); + } + + @Test + @DisplayName("PDFAFlavour exposes the level A profiles the converter now targets") + void flavoursExistForLevelA() { + assertNotNull(PDFAFlavour.PDFA_1_A); + assertNotNull(PDFAFlavour.PDFA_2_A); + assertNotNull(PDFAFlavour.PDFA_3_A); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/TaggedContentExtractorRealFilesTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/TaggedContentExtractorRealFilesTest.java new file mode 100644 index 0000000000..b42a429007 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ua/TaggedContentExtractorRealFilesTest.java @@ -0,0 +1,99 @@ +package stirling.software.proprietary.service.ua; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import stirling.software.proprietary.pdf.ua.PageContent; +import stirling.software.proprietary.pdf.ua.TaggedContentExtractor; + +/** + * Guards the invariant the tagger rests on: the token pass and text pass must agree on ordinals. + * They can silently disagree, and the extractor then drops the page rather than mis-tag it. + */ +class TaggedContentExtractorRealFilesTest { + + private static Path repoRoot; + + @BeforeAll + static void setUp() { + repoRoot = Path.of("").toAbsolutePath(); + while (repoRoot != null && !Files.exists(repoRoot.resolve("settings.gradle"))) { + repoRoot = repoRoot.getParent(); + } + } + + @Test + @DisplayName("pages with extractable text always yield lines across the repository corpus") + void ordinalsAgreeOnRealFiles() throws Exception { + assertNotNull(repoRoot, "could not locate the repository root"); + List dropped = new ArrayList<>(); + int inspected = 0; + + for (Path pdf : findPdfs()) { + byte[] bytes; + try { + bytes = Files.readAllBytes(pdf); + } catch (Exception e) { + continue; + } + try (PDDocument document = Loader.loadPDF(bytes)) { + if (document.getNumberOfPages() > 30) { + continue; + } + inspected++; + List pages = new TaggedContentExtractor().extract(document); + + for (PageContent page : pages) { + if (page.markableCount() == 0 || !hasText(document, page.pageIndex())) { + continue; + } + if (page.lines().isEmpty() && page.forms().isEmpty()) { + dropped.add(repoRoot.relativize(pdf) + " page " + page.pageIndex()); + } + } + } catch (Exception e) { + // Unreadable files are covered by the conversion tests. + } + } + + assertTrue(inspected > 15, "expected to inspect a real corpus, saw " + inspected); + assertTrue( + dropped.isEmpty(), + "the two extraction passes disagreed, so these pages were skipped: " + dropped); + } + + private static boolean hasText(PDDocument document, int pageIndex) { + try { + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setStartPage(pageIndex + 1); + stripper.setEndPage(pageIndex + 1); + return !stripper.getText(document).isBlank(); + } catch (Exception e) { + return false; + } + } + + private List findPdfs() throws Exception { + try (Stream stream = Files.walk(repoRoot)) { + return stream.filter(Files::isRegularFile) + .filter(p -> p.toString().toLowerCase().endsWith(".pdf")) + .filter(p -> !p.toString().contains("node_modules")) + .filter(p -> !p.toString().contains(java.io.File.separator + "build")) + .filter(p -> !p.toString().contains(".git")) + .toList(); + } + } +} diff --git a/app/saas/src/main/java/stirling/software/saas/config/SaasProjectNotice.java b/app/saas/src/main/java/stirling/software/saas/config/SaasProjectNotice.java new file mode 100644 index 0000000000..39305fcbcd --- /dev/null +++ b/app/saas/src/main/java/stirling/software/saas/config/SaasProjectNotice.java @@ -0,0 +1,53 @@ +package stirling.software.saas.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Profile; +import org.springframework.context.event.EventListener; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +/** Logs which Supabase project this backend is talking to, and its schema policy. */ +@Slf4j +@Component +@Profile({"dev", "staging"}) +public class SaasProjectNotice { + + private final Environment environment; + private final String projectRef; + private final String ddlAuto; + + public SaasProjectNotice( + Environment environment, + @Value("${app.supabase.project-ref:unknown}") String projectRef, + @Value("${spring.jpa.hibernate.ddl-auto:none}") String ddlAuto) { + this.environment = environment; + this.projectRef = projectRef; + this.ddlAuto = ddlAuto; + } + + @EventListener(ApplicationReadyEvent.class) + public void announceProject() { + boolean staging = environment.matchesProfiles("staging"); + if (staging) { + log.info( + """ + SaaS staging profile: Supabase project {}, ddl-auto={}. This is the SHARED \ + long-lived environment, so its data and schema are not yours alone. Testing an \ + open SaaS PR? Use that PR's preview branch instead \ + (SAAS_DEV_PROJECT_REF in app/.env.saas.local); staging will not have its \ + migrations.\ + """, + projectRef, + ddlAuto); + return; + } + log.info( + "SaaS dev profile: Supabase preview branch {}, ddl-auto={}. Disposable, so Hibernate" + + " is allowed to add the inherited tables the migrations do not create.", + projectRef, + ddlAuto); + } +} diff --git a/app/saas/src/main/java/stirling/software/saas/security/TeamLeaderPolicyManagementAuthority.java b/app/saas/src/main/java/stirling/software/saas/security/TeamLeaderPolicyManagementAuthority.java index e2f5b65b47..0ce5e8e308 100644 --- a/app/saas/src/main/java/stirling/software/saas/security/TeamLeaderPolicyManagementAuthority.java +++ b/app/saas/src/main/java/stirling/software/saas/security/TeamLeaderPolicyManagementAuthority.java @@ -25,6 +25,11 @@ public class TeamLeaderPolicyManagementAuthority implements PolicyManagementAuth return teamSecurity.isCurrentUserTeamLeader(); } + @Override + public boolean canTriggerPolicies() { + return teamSecurity.isCurrentUserTeamLeader(); + } + @Override public Long currentUserTeamId() { return teamSecurity.currentUserTeamId(); diff --git a/app/saas/src/main/resources/application-dev.properties b/app/saas/src/main/resources/application-dev.properties index ee8bf80ff2..b289fc95cc 100644 --- a/app/saas/src/main/resources/application-dev.properties +++ b/app/saas/src/main/resources/application-dev.properties @@ -1,32 +1,40 @@ -# SaaS dev profile. Points at the dev Supabase project. -# Boot: java -jar stirling-pdf.jar --spring.profiles.include=dev +# SaaS dev profile: follows the Supabase preview branch of the SaaS PR under test. +# One variable switches PR, SAAS_DEV_PROJECT_REF; everything else derives from it. +# Want a stable shared environment instead? Use the staging profile. + spring.config.import=optional:classpath:application-dev-local.properties -app.supabase.project-ref=qacaivhsjtftfwtgjvva +# Let Hibernate reconcile the entity tables so a fresh preview branch heals itself. A branch is built +# from the Supabase migrations, which cover the SaaS-owned tables but not the ~28 inherited from the +# self-hosted app -- those have only ever been created by ddl-auto. Safe here because a preview branch +# is disposable and `update` only ever adds; staging pins `none`, so keep this profile-scoped. +spring.jpa.hibernate.ddl-auto=update -stirling.supabase.url=https://qacaivhsjtftfwtgjvva.supabase.co -stirling.supabase.publishable-key=sb_publishable_nIM8y-9ARPE7EzQwAQHKMg_40fCN6kY # gitleaks:allow +# From the PR's "Supabase Preview" check. Required with no fallback: ddl-auto=update above must never +# be aimed at the shared project. +app.supabase.project-ref=${SAAS_DEV_PROJECT_REF} -spring.datasource.url=${SAAS_DEV_DB_URL:jdbc:postgresql://db.qacaivhsjtftfwtgjvva.supabase.co:5432/postgres?ApplicationName=stirling-consolidation-${user.name}} +stirling.supabase.url=https://${app.supabase.project-ref}.supabase.co +# Per-branch, not derivable. Dashboard > Settings > API. +stirling.supabase.publishable-key=${SAAS_DEV_PUBLISHABLE_KEY} + +# Override the whole URL if the branch needs the pooler host rather than the direct one. +spring.datasource.url=${SAAS_DEV_DB_URL:jdbc:postgresql://db.${app.supabase.project-ref}.supabase.co:5432/postgres?ApplicationName=stirling-dev-${user.name}} spring.datasource.username=${SAAS_DEV_DB_USERNAME:postgres} -# Password not committed; export SAAS_DEV_DB_PASSWORD or pass --spring.datasource.password=... +# A preview branch has its own password; the parent project's will not authenticate. spring.datasource.password=${SAAS_DEV_DB_PASSWORD:} -# Conservative dev pool sizing. spring.datasource.hikari.maximum-pool-size=2 spring.datasource.hikari.minimum-idle=1 spring.datasource.hikari.idle-timeout=60000 spring.datasource.hikari.max-lifetime=1800000 spring.datasource.hikari.keepalive-time=300000 -spring.datasource.hikari.data-source-properties.ApplicationName=stirling-consolidation-${user.name} +spring.datasource.hikari.data-source-properties.ApplicationName=stirling-dev-${user.name} logging.level.stirling.software.saas=DEBUG logging.level.org.springframework.security.oauth2.jwt=WARN logging.level.org.springframework.security.oauth2.server.resource=WARN -# Supabase meter edge fn the Java backend calls (server-to-server, on job close). -# URL is not a secret; auth rides the existing SUPABASE_EDGE_FUNCTION_SECRET (same -# shared secret the team-invitation flow uses — no service-role key in the Java env). -# Blank secret → the meter service no-ops with a WARN, so the app still boots. -# The billing portal is NOT here — the FE calls create-customer-portal-session directly. -payg.meter.endpoint=https://qacaivhsjtftfwtgjvva.supabase.co/functions/v1/meter-payg-units +# Server-to-server meter call. Auth rides SUPABASE_EDGE_FUNCTION_SECRET; blank secret means the meter +# service no-ops with a WARN rather than failing the boot. +payg.meter.endpoint=https://${app.supabase.project-ref}.supabase.co/functions/v1/meter-payg-units diff --git a/app/saas/src/main/resources/application-staging.properties b/app/saas/src/main/resources/application-staging.properties new file mode 100644 index 0000000000..4874ed3fb5 --- /dev/null +++ b/app/saas/src/main/resources/application-staging.properties @@ -0,0 +1,39 @@ +# SaaS staging profile: the long-lived shared v3 project, pinned so it is still there tomorrow. +# For work on an open SaaS PR use the dev profile, which follows that PR's preview branch. + +spring.config.import=optional:classpath:application-staging-local.properties + +# Stated rather than inherited: application-saas.properties defaults to `update`, and staging's +# schema is shared and RLS-dependent, so it must not be reconciled by Hibernate. +spring.jpa.hibernate.ddl-auto=none + +# Committed as a default rather than a literal, so staging needs no setup but stays repointable. +# Neither the ref nor the publishable key is secret: the ref is a public subdomain, the key ships in +# the browser bundle. Everything below derives from the ref, so an override follows through. +app.supabase.project-ref=${SAAS_STAGING_PROJECT_REF:qacaivhsjtftfwtgjvva} + +stirling.supabase.url=https://${app.supabase.project-ref}.supabase.co +stirling.supabase.publishable-key=${SAAS_STAGING_PUBLISHABLE_KEY:sb_publishable_nIM8y-9ARPE7EzQwAQHKMg_40fCN6kY} + +spring.datasource.url=${SAAS_STAGING_DB_URL:jdbc:postgresql://db.${app.supabase.project-ref}.supabase.co:5432/postgres?ApplicationName=stirling-staging-${user.name}} +spring.datasource.username=${SAAS_STAGING_DB_USERNAME:postgres} +# Password not committed; export SAAS_STAGING_DB_PASSWORD or pass --spring.datasource.password=... +spring.datasource.password=${SAAS_STAGING_DB_PASSWORD:} + +# Conservative pool sizing: this is a shared project, so don't hold connections others need. +spring.datasource.hikari.maximum-pool-size=2 +spring.datasource.hikari.minimum-idle=1 +spring.datasource.hikari.idle-timeout=60000 +spring.datasource.hikari.max-lifetime=1800000 +spring.datasource.hikari.keepalive-time=300000 +spring.datasource.hikari.data-source-properties.ApplicationName=stirling-staging-${user.name} + +logging.level.stirling.software.saas=DEBUG +logging.level.org.springframework.security.oauth2.jwt=WARN +logging.level.org.springframework.security.oauth2.server.resource=WARN + +# Supabase meter edge fn the Java backend calls (server-to-server, on job close). +# URL is not a secret; auth rides the existing SUPABASE_EDGE_FUNCTION_SECRET (same +# shared secret the team-invitation flow uses — no service-role key in the Java env). +# Blank secret → the meter service no-ops with a WARN, so the app still boots. +payg.meter.endpoint=https://${app.supabase.project-ref}.supabase.co/functions/v1/meter-payg-units diff --git a/app/saas/src/test/java/stirling/software/saas/security/TeamLeaderPolicyManagementAuthorityTest.java b/app/saas/src/test/java/stirling/software/saas/security/TeamLeaderPolicyManagementAuthorityTest.java index 70cd360d7c..2c37980a5c 100644 --- a/app/saas/src/test/java/stirling/software/saas/security/TeamLeaderPolicyManagementAuthorityTest.java +++ b/app/saas/src/test/java/stirling/software/saas/security/TeamLeaderPolicyManagementAuthorityTest.java @@ -32,6 +32,18 @@ class TeamLeaderPolicyManagementAuthorityTest { assertFalse(authority().canEditPolicies()); } + @Test + void teamLeaderMayTriggerPolicies() { + when(teamSecurity.isCurrentUserTeamLeader()).thenReturn(true); + assertTrue(authority().canTriggerPolicies()); + } + + @Test + void nonLeaderMayNotTriggerPolicies() { + when(teamSecurity.isCurrentUserTeamLeader()).thenReturn(false); + assertFalse(authority().canTriggerPolicies()); + } + @Test void currentUserTeamIdDelegatesToTeamSecurity() { when(teamSecurity.currentUserTeamId()).thenReturn(9L); diff --git a/build.gradle b/build.gradle index cadf4bafa6..8b6dee0262 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ import stirling.software.gradle.ModuleLicenseOverrideFilter ext { springBootVersion = "4.0.6" pdfboxVersion = "3.0.8" - imageioVersion = "3.13.1" + imageioVersion = "3.14.0" lombokVersion = "1.18.46" bouncycastleVersion = "1.85" springSecuritySamlVersion = "7.1.0" @@ -108,7 +108,7 @@ springBoot { allprojects { group = 'stirling.software' - version = '2.14.2' + version = '2.14.3' configurations.configureEach { exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index fd26ee2fb7..0d5a310c45 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -4,7 +4,7 @@ ARG BASE_VERSION=1.0.2@sha256:c7698687f486707ddef9e0298587ca8b44c4e96185e1bdb0c3 ARG BASE_IMAGE=stirlingtools/stirling-pdf-base:${BASE_VERSION} # Stage 1: Build the Java application (backend only, no frontend) -FROM gradle:9.6.1-jdk25@sha256:e8aeffb8197b17151ce24607811f60e91125f0018f7a3b08dc504ba9168a9c4f AS app-build +FROM gradle:9.7.1-jdk25@sha256:a80276ab804c348989df46016e2b5d58cad07c5b29e06f2112434d28ca5b2844 AS app-build # JDK 25+: --add-exports is no longer accepted via JAVA_TOOL_OPTIONS; use JDK_JAVA_OPTIONS instead ENV JDK_JAVA_OPTIONS="--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 95e4e84795..06e1b6e601 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -7,7 +7,7 @@ ARG TARGETPLATFORM # Stage 1: Build and strip Calibre FROM ubuntu:noble@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea AS calibre-build ARG TARGETPLATFORM -ARG CALIBRE_VERSION=9.4.0 +ARG CALIBRE_VERSION=9.13.0 ARG CALIBRE_STRIP_WEBENGINE=false RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ @@ -17,7 +17,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ca-certificates curl xz-utils libnss3 libfontconfig1 \ libgl1 libegl1 libdbus-1-3 libasound2t64 libxcomposite1 \ libxrandr2 libxkbcommon0 libxi6 libxtst6 libopengl0 \ - poppler-utils; \ + poppler-utils tzdata; \ rm -rf /var/lib/apt/lists/*; \ \ case "$(uname -m)" in \ @@ -218,8 +218,12 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ # Keep iso639.calibre_msgpack (required) # Keep only en-US.pak from qtwebengine_locales (required for WebEngine) rm -rf /opt/calibre/lib/qt6/translations; \ - find /opt/calibre/translations -mindepth 1 -maxdepth 1 ! -name 'qtwebengine_locales' -exec rm -rf {} +; \ - find /opt/calibre/translations/qtwebengine_locales -type f ! -name 'en-US.pak' -delete 2>/dev/null || true; \ + if [ -d /opt/calibre/translations ]; then \ + find /opt/calibre/translations -mindepth 1 -maxdepth 1 ! -name 'qtwebengine_locales' -exec rm -rf {} + 2>/dev/null || true; \ + if [ -d /opt/calibre/translations/qtwebengine_locales ]; then \ + find /opt/calibre/translations/qtwebengine_locales -type f ! -name 'en-US.pak' -delete 2>/dev/null || true; \ + fi; \ + fi; \ if [ -d /opt/calibre/resources/localization ]; then \ rm -rf /opt/calibre/resources/localization/locales.zip \ /opt/calibre/resources/localization/stats.calibre_msgpack \ @@ -272,7 +276,7 @@ RUN if [ "${CALIBRE_STRIP_WEBENGINE}" = "true" ]; then \ # Stage 2: Build Ghostscript from source FROM ubuntu:noble@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea AS gs-build ARG TARGETPLATFORM -ARG GS_VERSION=10.06.0 +ARG GS_VERSION=10.07.1 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/tmp/gs-build,id=gs-build-${TARGETPLATFORM:-local} \ @@ -296,8 +300,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ # Stage 3: Build PDF Tools (QPDF and ImageMagick 7) FROM ubuntu:noble@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea AS pdf-tools-build ARG TARGETPLATFORM -ARG QPDF_VERSION=12.3.2 -ARG IM_VERSION=7.1.2-13 +ARG QPDF_VERSION=12.4.0 +ARG IM_VERSION=7.1.2-29 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/tmp/pdf-tools-build,id=pdf-tools-${TARGETPLATFORM:-local} \ @@ -341,7 +345,7 @@ RUN mkdir -p /magick-export/usr/bin \ # Stage 4: Build Python venv FROM ubuntu:noble@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea AS python-venv-build ARG TARGETPLATFORM -ARG UNOSERVER_VERSION=3.6 +ARG UNOSERVER_VERSION=3.7 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends \ @@ -373,7 +377,7 @@ ENV DEBIAN_FRONTEND=noninteractive \ LC_ALL=C.UTF-8 \ TESS_BASE_PATH=/usr/share/tesseract-ocr/5/tessdata -ARG UNOSERVER_VERSION=3.6 +ARG UNOSERVER_VERSION=3.7 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ set -eux; \ diff --git a/docker/embedded/Dockerfile b/docker/embedded/Dockerfile index d4ad422476..7289d67bb5 100644 --- a/docker/embedded/Dockerfile +++ b/docker/embedded/Dockerfile @@ -5,9 +5,9 @@ ARG BASE_VERSION=1.0.2@sha256:c7698687f486707ddef9e0298587ca8b44c4e96185e1bdb0c3 ARG BASE_IMAGE=stirlingtools/stirling-pdf-base:${BASE_VERSION} # Stage 1: Build the Java application and frontend -FROM gradle:9.6.1-jdk25@sha256:e8aeffb8197b17151ce24607811f60e91125f0018f7a3b08dc504ba9168a9c4f AS app-build +FROM gradle:9.7.1-jdk25@sha256:a80276ab804c348989df46016e2b5d58cad07c5b29e06f2112434d28ca5b2844 AS app-build -ARG TASK_VERSION=3.49.1 +ARG TASK_VERSION=3.52.0 RUN apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ && update-ca-certificates \ diff --git a/docker/embedded/Dockerfile.fat b/docker/embedded/Dockerfile.fat index 58eafec851..542bdec981 100644 --- a/docker/embedded/Dockerfile.fat +++ b/docker/embedded/Dockerfile.fat @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1 + # Stirling-PDF - Fat version (embedded frontend) # Extra fonts for air-gapped environments # Uses pre-built base image for fast builds @@ -6,9 +8,9 @@ ARG BASE_VERSION=1.0.2@sha256:c7698687f486707ddef9e0298587ca8b44c4e96185e1bdb0c3 ARG BASE_IMAGE=stirlingtools/stirling-pdf-base:${BASE_VERSION} # Stage 1: Build the Java application and frontend -FROM gradle:9.6.1-jdk25@sha256:e8aeffb8197b17151ce24607811f60e91125f0018f7a3b08dc504ba9168a9c4f AS app-build +FROM gradle:9.7.1-jdk25@sha256:a80276ab804c348989df46016e2b5d58cad07c5b29e06f2112434d28ca5b2844 AS app-build -ARG TASK_VERSION=3.49.1 +ARG TASK_VERSION=3.52.0 RUN apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ && update-ca-certificates \ @@ -38,7 +40,8 @@ COPY app/common/build.gradle app/common/ COPY app/proprietary/build.gradle app/proprietary/ # Use system gradle instead of gradlew to avoid SSL issues downloading gradle distribution on emulated arm64 -RUN gradle dependencies --no-daemon || true +RUN --mount=type=cache,id=stirling-pdf-gradle-cache,target=/home/gradle/.gradle,sharing=locked \ + gradle dependencies --no-daemon || true COPY . . @@ -46,7 +49,9 @@ COPY . . ARG BUILD_PORTAL=false # Bundle only the JPDFium native for this image's target arch. ARG TARGETARCH -RUN JPDFIUM_PLATFORM="$([ "$TARGETARCH" = arm64 ] && echo linux-arm64 || echo linux-x64)" && \ +RUN --mount=type=cache,id=stirling-pdf-npm-cache,target=/root/.npm,sharing=locked \ + --mount=type=cache,id=stirling-pdf-gradle-cache,target=/home/gradle/.gradle,sharing=locked \ + JPDFIUM_PLATFORM="$([ "$TARGETARCH" = arm64 ] && echo linux-arm64 || echo linux-x64)" && \ DISABLE_ADDITIONAL_FEATURES=false \ gradle clean build \ -PbuildWithFrontend=true \ diff --git a/docker/embedded/Dockerfile.ultra-lite b/docker/embedded/Dockerfile.ultra-lite index d4a21b7812..c21ddf2f8d 100644 --- a/docker/embedded/Dockerfile.ultra-lite +++ b/docker/embedded/Dockerfile.ultra-lite @@ -1,11 +1,13 @@ +# syntax=docker/dockerfile:1 + # Stirling-PDF Dockerfile - Ultra-lite version with embedded frontend # Single JAR contains both frontend and backend with minimal dependencies # Stage 1: Build application with embedded frontend -FROM gradle:9.6.1-jdk25@sha256:e8aeffb8197b17151ce24607811f60e91125f0018f7a3b08dc504ba9168a9c4f AS build +FROM gradle:9.7.1-jdk25@sha256:a80276ab804c348989df46016e2b5d58cad07c5b29e06f2112434d28ca5b2844 AS build # Install Node.js and npm for frontend build -ARG TASK_VERSION=3.49.1 +ARG TASK_VERSION=3.52.0 RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ @@ -36,7 +38,8 @@ ENV JDK_JAVA_OPTIONS="--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNN --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" -RUN ./gradlew dependencies --no-daemon || true +RUN --mount=type=cache,id=stirling-pdf-gradle-cache,target=/home/gradle/.gradle,sharing=locked \ + ./gradlew dependencies --no-daemon || true # Copy entire project COPY . . @@ -46,7 +49,9 @@ COPY . . ARG BUILD_PORTAL=false # Bundle only the JPDFium native for this image's target arch. ARG TARGETARCH -RUN JPDFIUM_PLATFORM="$([ "$TARGETARCH" = arm64 ] && echo linux-arm64 || echo linux-x64)" && \ +RUN --mount=type=cache,id=stirling-pdf-npm-cache,target=/root/.npm,sharing=locked \ + --mount=type=cache,id=stirling-pdf-gradle-cache,target=/home/gradle/.gradle,sharing=locked \ + JPDFIUM_PLATFORM="$([ "$TARGETARCH" = arm64 ] && echo linux-arm64 || echo linux-x64)" && \ DISABLE_ADDITIONAL_FEATURES=true \ ./gradlew clean build \ -PbuildWithFrontend=true \ diff --git a/docker/unoserver/Dockerfile b/docker/unoserver/Dockerfile index 5bd887c8ed..da5ba27371 100644 --- a/docker/unoserver/Dockerfile +++ b/docker/unoserver/Dockerfile @@ -1,9 +1,9 @@ # Standalone unoserver image for Stirling-PDF remote UNO mode. -# Pinned to unoserver 3.6 to match Stirling-PDF's client (avoids 3.4/3.6 wire mismatch). +# Pinned to unoserver 3.7 to match Stirling-PDF's client (avoids wire mismatch). FROM ubuntu:noble@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea -ARG UNOSERVER_VERSION=3.6 +ARG UNOSERVER_VERSION=3.7 # ~120 MB of CJK fonts — opt-in. ARG INSTALL_CJK_FONTS=false diff --git a/engine/Dockerfile b/engine/Dockerfile index d9bde0f89d..17a917761b 100644 --- a/engine/Dockerfile +++ b/engine/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1.5 FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim@sha256:531f855bda2c73cd6ef67d56b733b357cea384185b3022bd09f05e002cd144ca -ARG TASK_VERSION=3.49.1 +ARG TASK_VERSION=3.52.0 RUN apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ && ARCH=$(dpkg --print-architecture) \ diff --git a/engine/pyproject.toml b/engine/pyproject.toml index 87e7cc99c0..0e4ffb1586 100644 --- a/engine/pyproject.toml +++ b/engine/pyproject.toml @@ -21,7 +21,7 @@ engine = [ "pydantic-settings>=2.15.0", "python-dotenv>=1.2.2", "sqlite-vec>=0.1.9", - "uvicorn>=0.52.1", + "uvicorn>=0.52.3", ] # Type checking, testing, model generation, and formatting tools for the engine. engine-dev = [ @@ -49,7 +49,7 @@ tools = [ "deep-translator>=1.11.4", "defusedxml>=0.7.1", "fonttools>=4.63.0", - "fpdf2>=2.8.7", + "fpdf2>=2.8.8", "openai>=2.53.0", "requests>=2.34.2", "tomli-w>=1.2.0", diff --git a/engine/src/stirling/models/tool_io.py b/engine/src/stirling/models/tool_io.py index da0cd5b9ae..c9da9cce90 100644 --- a/engine/src/stirling/models/tool_io.py +++ b/engine/src/stirling/models/tool_io.py @@ -126,6 +126,7 @@ TOOL_IO: dict[ToolEndpoint, ToolIOSpec] = { ) ], ), + ToolEndpoint.PDF_TO_UA: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO), ToolEndpoint.PDF_TO_VECTOR: ToolIOSpec( accepts=[ToolFormat.PDF], produces=ToolFormat.IMAGE, @@ -246,6 +247,9 @@ TOOL_IO: dict[ToolEndpoint, ToolIOSpec] = { ToolEndpoint.SCANNER_EFFECT: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO), ToolEndpoint.UNLOCK_PDF_FORMS: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO), ToolEndpoint.UPDATE_METADATA: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO), + ToolEndpoint.ACCESSIBILITY_REPORT: ToolIOSpec( + accepts=[ToolFormat.PDF], produces=ToolFormat.JSON, arity=ToolArity.SISO + ), ToolEndpoint.ADD_PASSWORD: ToolIOSpec( accepts=[ToolFormat.PDF], produces=ToolFormat.PDF_ENCRYPTED, diff --git a/engine/src/stirling/models/tool_models.py b/engine/src/stirling/models/tool_models.py index 712e3575af..6650942b6f 100644 --- a/engine/src/stirling/models/tool_models.py +++ b/engine/src/stirling/models/tool_models.py @@ -11,6 +11,19 @@ from pydantic import Field, RootModel, SecretStr from stirling.models.base import ApiModel +class Profile(StrEnum): + """ + Profile to check against + """ + + ua1 = "ua1" + ua2 = "ua2" + + +class AccessibilityReportParams(ApiModel): + profile: Profile = Field(Profile.ua1, description="Profile to check against") + + class AddCommentsParams(ApiModel): comments: str = Field( ..., @@ -843,11 +856,18 @@ class OutputFormat1(StrEnum): pdfa_2b = "pdfa-2b" pdfa_3 = "pdfa-3" pdfa_3b = "pdfa-3b" + pdfa_1a = "pdfa-1a" + pdfa_2a = "pdfa-2a" + pdfa_3a = "pdfa-3a" pdfx = "pdfx" class PdfToPdfaParams(ApiModel): output_format: OutputFormat1 = Field(..., description="The output format type (PDF/A or PDF/X)") + pdf_ua: bool = Field( + False, + description="Also declare PDF/UA accessibility alongside PDF/A. Only applies to the level A formats, and the claim is written only if it validates.", + ) strict: bool | None = Field( None, description="If true, the conversion will fail if the output is not perfectly compliant" ) @@ -886,6 +906,65 @@ class PdfToTextParams(ApiModel): output_format: OutputFormat3 = Field(..., description="The output Text or RTF format") +class ExistingTags(StrEnum): + """ + What to do with an existing structure tree: keep it, rebuild it, or decide automatically + """ + + auto = "auto" + keep = "keep" + rebuild = "rebuild" + + +class FigurePolicy(StrEnum): + """ + How to treat images with no description. require-alt leaves them undescribed so the report asks for input; mark-decorative treats every image as decoration. + """ + + require_alt = "require-alt" + mark_decorative = "mark-decorative" + + +class Profile1(StrEnum): + """ + PDF/UA conformance level to target + """ + + ua1 = "ua1" + ua2 = "ua2" + + +class PdfToUaParams(ApiModel): + alt_text: str | None = Field( + None, + description='Alternative descriptions for figures, as key=text pairs separated by newlines. Keys come from the accessibility-report endpoint\'s figuresNeedingDescription list, for example "0:12=Bar chart of quarterly revenue". Descriptions are never invented, so without these an illustrated document cannot claim conformance.', + ) + embed_fonts: bool = Field( + True, + description="Embed fonts the document references but does not carry. Required for conformance and needs Ghostscript.", + ) + existing_tags: ExistingTags = Field( + ExistingTags.auto, + description="What to do with an existing structure tree: keep it, rebuild it, or decide automatically", + ) + figure_policy: FigurePolicy = Field( + FigurePolicy.require_alt, + description="How to treat images with no description. require-alt leaves them undescribed so the report asks for input; mark-decorative treats every image as decoration.", + ) + language: str = Field( + "en-GB", + description="Document language as a BCP-47 tag, for example en-GB. Applied only when the document does not already declare one, unless overrideLanguage is set.", + ) + override_language: bool = Field( + False, + description="Replace the language the document already declares. Off by default, so a document is never relabelled into a language it is not written in.", + ) + profile: Profile1 = Field(Profile1.ua1, description="PDF/UA conformance level to target") + title: str | None = Field( + None, description="Document title, required by PDF/UA. Falls back to the first heading, then the filename." + ) + + class OutputFormat4(StrEnum): """ Target vector format extension @@ -1451,6 +1530,7 @@ class Model( | PdfToPdfaParams | PdfToPresentationParams | PdfToTextParams + | PdfToUaParams | PdfToVectorParams | PdfToWordParams | PdfToXlsxParams @@ -1495,6 +1575,7 @@ class Model( | ScannerEffectParams | UnlockPdfFormsParams | UpdateMetadataParams + | AccessibilityReportParams | AddPasswordParams | AddWatermarkParams | AutoRedactParams @@ -1525,6 +1606,7 @@ class Model( | PdfToPdfaParams | PdfToPresentationParams | PdfToTextParams + | PdfToUaParams | PdfToVectorParams | PdfToWordParams | PdfToXlsxParams @@ -1569,6 +1651,7 @@ class Model( | ScannerEffectParams | UnlockPdfFormsParams | UpdateMetadataParams + | AccessibilityReportParams | AddPasswordParams | AddWatermarkParams | AutoRedactParams @@ -1600,6 +1683,7 @@ type ParamToolModel = ( | PdfToPdfaParams | PdfToPresentationParams | PdfToTextParams + | PdfToUaParams | PdfToVectorParams | PdfToWordParams | PdfToXlsxParams @@ -1644,6 +1728,7 @@ type ParamToolModel = ( | ScannerEffectParams | UnlockPdfFormsParams | UpdateMetadataParams + | AccessibilityReportParams | AddPasswordParams | AddWatermarkParams | AutoRedactParams @@ -1676,6 +1761,7 @@ class ToolEndpoint(StrEnum): PDF_TO_PDFA = "/api/v1/convert/pdf/pdfa" PDF_TO_PRESENTATION = "/api/v1/convert/pdf/presentation" PDF_TO_TEXT = "/api/v1/convert/pdf/text" + PDF_TO_UA = "/api/v1/convert/pdf/ua" PDF_TO_VECTOR = "/api/v1/convert/pdf/vector" PDF_TO_WORD = "/api/v1/convert/pdf/word" PDF_TO_XLSX = "/api/v1/convert/pdf/xlsx" @@ -1720,6 +1806,7 @@ class ToolEndpoint(StrEnum): SCANNER_EFFECT = "/api/v1/misc/scanner-effect" UNLOCK_PDF_FORMS = "/api/v1/misc/unlock-pdf-forms" UPDATE_METADATA = "/api/v1/misc/update-metadata" + ACCESSIBILITY_REPORT = "/api/v1/security/accessibility-report" ADD_PASSWORD = "/api/v1/security/add-password" ADD_WATERMARK = "/api/v1/security/add-watermark" AUTO_REDACT = "/api/v1/security/auto-redact" @@ -1750,6 +1837,7 @@ OPERATIONS: dict[ToolEndpoint, ParamToolModelType] = { ToolEndpoint.PDF_TO_PDFA: PdfToPdfaParams, ToolEndpoint.PDF_TO_PRESENTATION: PdfToPresentationParams, ToolEndpoint.PDF_TO_TEXT: PdfToTextParams, + ToolEndpoint.PDF_TO_UA: PdfToUaParams, ToolEndpoint.PDF_TO_VECTOR: PdfToVectorParams, ToolEndpoint.PDF_TO_WORD: PdfToWordParams, ToolEndpoint.PDF_TO_XLSX: PdfToXlsxParams, @@ -1794,6 +1882,7 @@ OPERATIONS: dict[ToolEndpoint, ParamToolModelType] = { ToolEndpoint.SCANNER_EFFECT: ScannerEffectParams, ToolEndpoint.UNLOCK_PDF_FORMS: UnlockPdfFormsParams, ToolEndpoint.UPDATE_METADATA: UpdateMetadataParams, + ToolEndpoint.ACCESSIBILITY_REPORT: AccessibilityReportParams, ToolEndpoint.ADD_PASSWORD: AddPasswordParams, ToolEndpoint.ADD_WATERMARK: AddWatermarkParams, ToolEndpoint.AUTO_REDACT: AutoRedactParams, diff --git a/engine/uv.lock b/engine/uv.lock index fbfa665bc6..bb5b812fce 100644 --- a/engine/uv.lock +++ b/engine/uv.lock @@ -707,7 +707,7 @@ engine = [ { name = "pydantic-settings", specifier = ">=2.15.0" }, { name = "python-dotenv", specifier = ">=1.2.2" }, { name = "sqlite-vec", specifier = ">=0.1.9" }, - { name = "uvicorn", specifier = ">=0.52.1" }, + { name = "uvicorn", specifier = ">=0.52.3" }, ] engine-dev = [ { name = "anyio", specifier = ">=4.14.2" }, @@ -726,7 +726,7 @@ tools = [ { name = "deep-translator", specifier = ">=1.11.4" }, { name = "defusedxml", specifier = ">=0.7.1" }, { name = "fonttools", specifier = ">=4.63.0" }, - { name = "fpdf2", specifier = ">=2.8.7" }, + { name = "fpdf2", specifier = ">=2.8.8" }, { name = "openai", specifier = ">=2.53.0" }, { name = "requests", specifier = ">=2.34.2" }, { name = "tomli-w", specifier = ">=1.2.0" }, @@ -898,16 +898,16 @@ woff = [ [[package]] name = "fpdf2" -version = "2.8.7" +version = "2.8.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "defusedxml" }, { name = "fonttools" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/f2/72feae0b2827ed38013e4307b14f95bf0b3d124adfef4d38a7d57533f7be/fpdf2-2.8.7.tar.gz", hash = "sha256:7060ccee5a9c7ab0a271fb765a36a23639f83ef8996c34e3d46af0a17ede57f9", size = 362351, upload-time = "2026-02-28T05:39:16.456Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/bc/8fd4321aed40cadadddc8f311c65b6082346b252bca048f7b476d8f35d72/fpdf2-2.8.8.tar.gz", hash = "sha256:9e94e155e85e8053329a9a1fce8b566fd7a7c5bb79e98a1a3952d379b947c5b9", size = 374689, upload-time = "2026-08-09T23:32:45.334Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/0a/cf50ecffa1e3747ed9380a3adfc829259f1f86b3fdbd9e505af789003141/fpdf2-2.8.7-py3-none-any.whl", hash = "sha256:d391fc508a3ce02fc43a577c830cda4fe6f37646f2d143d489839940932fbc19", size = 327056, upload-time = "2026-02-28T05:39:14.619Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/af012eda9507494f28b99b077423806c43a11573eb6225dd46f19ae2d263/fpdf2-2.8.8-py3-none-any.whl", hash = "sha256:3557a478fc577a929c94aace9666aed4dcc432b5ab6764232e6a59f1ccd75f17", size = 337000, upload-time = "2026-08-09T23:32:43.728Z" }, ] [[package]] @@ -3188,15 +3188,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.52.1" +version = "0.52.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/18/ccce41535dee1be77735592bd19965f3972c82e07ee703d324709496b716/uvicorn-0.52.1.tar.gz", hash = "sha256:112ec661814189acbccd3f7b86460147cc065fc92c0821afa78918780e4354dd", size = 100571, upload-time = "2026-08-01T18:19:30.732Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/28/64ca011edf31c715b4fad359c587ea52391aaffa125065695590241ff617/uvicorn-0.52.3.tar.gz", hash = "sha256:18857b9e6579300be55c91c0a1cfd37d9a2cf0cabea33b88275f199eb73b8b58", size = 100621, upload-time = "2026-08-13T16:50:02.899Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/d5/68e6e9bca63c0badf67002890a46d3784c958de45b65e1275ec583ca1f06/uvicorn-0.52.1-py3-none-any.whl", hash = "sha256:e4403f9d93188cf9d1088e9f40e3acd12630e2df8675316704379a7fc20fff6a", size = 79859, upload-time = "2026-08-01T18:19:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2b/ebd108734a8204c6b4b93c681c9a38c5273b3ccd5d129fee4ffc1d97772c/uvicorn-0.52.3-py3-none-any.whl", hash = "sha256:116af2710dbf47c80f463cd20ee4884b6662f4c9f227d797ddc7279d2fcc2c7c", size = 79859, upload-time = "2026-08-13T16:50:01.323Z" }, ] [[package]] diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx index f568b34347..5d36a0ca16 100644 --- a/frontend/.storybook/preview.tsx +++ b/frontend/.storybook/preview.tsx @@ -71,9 +71,9 @@ for (const [path, raw] of Object.entries(localeModules)) { } if (!i18next.isInitialized) { - // initImmediate: false → initialise synchronously from the inline resources - // (there's no async backend here), so i18next is ready before the first story - // renders. Without it the first render can beat init and stick on raw keys. + // initAsync: false → initialise synchronously from the inline resources. + // There is no asynchronous backend here, so i18next is ready before stories + // render. void i18next.use(initReactI18next).init({ lng: "en-US", fallbackLng: "en-US", @@ -81,7 +81,7 @@ if (!i18next.isInitialized) { resources, interpolation: { escapeValue: false }, react: { useSuspense: false }, - initImmediate: false, + initAsync: false, }); } else { // Something initialised i18next first (e.g. the app's async TOML backend): diff --git a/frontend/editor/index.html b/frontend/editor/index.html index 427b6d1449..1e782db2de 100644 --- a/frontend/editor/index.html +++ b/frontend/editor/index.html @@ -4,7 +4,10 @@ - + |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "إضافة إلى مساحة العمل" addToWorkspaceCount = "إضافة {{count}} إلى مساحة العمل" @@ -3918,7 +3971,6 @@ backToMyFiles = "الرجوع إلى ملفاتي" breadcrumbs = "مسار المجلد" cancel = "إلغاء" classification = "التصنيف" -clearSearch = "مسح البحث" clearSelection = "مسح التحديد" closeDetails = "إغلاق التفاصيل" create = "إنشاء" @@ -3950,6 +4002,7 @@ downloadAll = "تنزيل الكل" downloadVersion = "تنزيل هذا الإصدار" dropOverlay = "أفلِت الملفات للرفع" dropOverlaySub = "تبدأ الملفات محلياً. استخدم 'نقل إلى' أو 'حفظ إلى السحابة' لتنظيمها في مجلد." +duplicate = "Duplicate" file = "ملف" fileInfo = "File info" fileMenu = "إجراءات الملف" @@ -3983,8 +4036,6 @@ resizeFolderTree = "تغيير حجم شجرة المجلدات (مفاتيح ا save = "حفظ" saveToServer = "حفظ إلى الخادم" saveToServerDisabledHint = "الحفظ إلى الخادم غير مفعّل على هذا الخادم. اطلب من المسؤول تمكينه." -search = "بحث" -searchPlaceholder = "ابحث في هذا المجلد والمجلدات الفرعية" selectAll = "تحديد الكل" selectAllHint = "انقر لتحديد الكل. تلميح: اضغط Ctrl (أو Cmd) لإضافة الملفات واحداً تلو الآخر، وShift لتحديد نطاق." selectedCount = "{{count}} محدد" @@ -4044,6 +4095,10 @@ title = "لا توجد ملفات سحابية بعد" hint = "الملفات المحفوظة دون رفع تبقى هنا. أسقط ملفاً لإضافته." title = "لا توجد ملفات محلية فقط" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "الملفات التي تفتحها أو تعدّلها ستظهر هنا." title = "لم يتم تعديل أي شيء بعد" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "تعذّر حذف المجلد." deleteFolderFailedDetail = "تعذّر حذف المجلد: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "تعذّر تحديث مظهر المجلد." folderAppearanceFailedDetail = "تعذّر تحديث مظهر المجلد: {{message}}" moveFilesFailed = "تعذّر نقل الملفات." @@ -4113,6 +4170,11 @@ localHint = "مخزّن فقط في هذا المتصفح" shared = "مُشترك" sharedHint = "تمت مشاركته معك عبر رابط" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "الأقدم أولاً" modifiedDesc = "الأحدث أولاً" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "لا توجد ملفات في التخزين. افتح open = "فتح" openFile = "فتح ملف" openFiles = "فتح ملفات" +selectFile = "Select {{name}}" selectFromStorage = "الاختيار من التخزين" upload = "تحميل" uploadFile = "تحميل ملف" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "تم حفظ الكل" +analyzingFields = "Analysing form fields..." extractCsvError = "فشل استخراج CSV" extractXlsxError = "فشل استخراج XLSX" +filled = "filled" flattenAfterFilling = "تسطيح بعد التعبئة" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "مطلوب" requiredFieldsError = "يُرجى ملء جميع الحقول المطلوبة" rescanFields = "إعادة فحص الحقول" @@ -4795,9 +4863,6 @@ title = "صورة إلى PDF" [imageToPdf] tags = "تحويل,صورة,jpg,صورة,صورة فوتوغرافية" -[infoBanner] -dismiss = "تجاهل" - [invite] acceptError = "فشل إنشاء الحساب" accountFor = "إنشاء حساب لـ" @@ -4979,6 +5044,34 @@ uploadSuccess = "تم الرفع بنجاح!" uploadSuccessMessage = "تم نقل صورك." validating = "جارٍ التحقق من الجلسة..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "امسح ضوئيًا لرفع الصور. تتحول الصور تلقائيًا إلى PDF." descriptionNoConvert = "امسح ضوئيًا لرفع الصور من جهازك المحمول." @@ -4997,8 +5090,13 @@ title = "رفع من الهاتف المحمول" tags = "أداة متعددة,عملية متعددة,واجهة مستخدم,النقر والسحب,واجهة أمامية,جانب العميل" title = "أداة متعددة PDF" -[navbar] -search = "البحث" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "لم تتم المصادقة بنجاح. يمكنك إغلاق هذه النافذة والمحاولة مجدداً." @@ -5127,6 +5225,7 @@ activeFiles = "تعرض واجهة الملفات النشطة allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "إغلاق" cropSettings = "بعد أن اخترنا الملف الذي نريد قصّه، يمكننا تهيئة أداة القص لاختيار المنطقة التي سنقصّ إليها ملف PDF." +dialogLabel = "Onboarding" fileCheckbox = "يؤدي النقر على أحد الملفات إلى اختياره للمعالجة. يمكنك تحديد عدة ملفات لتنفيذ العمليات الدُفعية." fileReplacement = "سيحل الملف المعدّل محل الملف الأصلي في منضدة العمل تلقائيًا، مما يتيح لك تشغيله بسهولة عبر مزيد من الأدوات." filesButton = "يتيح زر الملفات في شريط الوصول السريع رفع ملفات PDF لاستخدام الأدوات عليها." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF إلى PDF/A" tags = "أرشيف,طويل الأجل,معيار,تحويل,تخزين,حفظ" title = "PDF إلى PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "طباعة,معيار,تحويل,إنتاج,ما قبل الطباعة,أرشفة" title = "PDF إلى PDF/X" @@ -6045,6 +6151,7 @@ small = "500 رصيد" xsmall = "100 رصيد" [plan.availablePlans] +currency = "Billing currency" subtitle = "اختر الخطة التي تناسب احتياجاتك" title = "الخطط المتاحة" @@ -6624,6 +6731,7 @@ reachedTitle = "تم بلوغ حد الإنفاق الشهري" title = "تعذر فتح بوابة Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "من {{allowance}} ملفات PDF مجانية مستخدمة" capSuffix_other = "من {{allowance}} ملفات PDF مجانية مستخدمة" eyebrow = "تجربة Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "أرسل مستندًا أو إشعارًا بالبريد الإلكتروني عندما تعمل سياسة." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "حفظ المستندات المعالجة في Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDKs الرسمية" beta = "Beta" deprecated = "مهمل" -[portal.docs.search] -empty = "لا توجد مستندات مطابقة" -placeholder = "بحث في المستندات" -results = "{{count}} نتيجة" - [portal.docs.skills] eyebrow = "المهارات" lead = "قدرات مدمجة ومسمّاة يستدعيها وكيلك كأداة واحدة. كل مهارة هي سلسلة عمليات حتمية مع تقييمات مرفقة." @@ -7180,11 +7309,11 @@ editorAction = "المحرر" empty = "لا توجد مستندات تطابق عامل التصفية هذا." rowActions = "إجراءات الصف" sensitiveLabel = "حساس" -sensitiveTitle = "حساس — الوصول مطلوب" [portal.documents.table.columns] action = "مسار العمل / الإجراء" document = "المستند" +labels = "Labels" product = "المنتج" status = "الحالة" time = "الوقت" @@ -7200,6 +7329,7 @@ host = "المضيف" lastSeen = "آخر ظهور" region = "المنطقة" status = "الحالة" +target = "Target" version = "الإصدار" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "واجه هذا العرض خطأً غير متوقع. حاول retry = "حاول مرة أخرى" title = "حدث خطأ ما في هذه الصفحة" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "رجوع" body = "نزّل تطبيق سطح المكتب أو استضف الخادم ذاتيًا." @@ -7380,11 +7560,6 @@ cancel = "إلغاء" confirm = "إبطال المفتاح" title = "إبطال مفتاح API" -[portal.infrastructure.attestationLabel] -attested = "مُصدق عليه" -inScope = "ضمن النطاق" -notApplicable = "غير منطبق" - [portal.infrastructure.audit] filterAriaLabel = "تصفية أحداث التدقيق حسب الفئة" heading = "سجلات التدقيق" @@ -7456,11 +7631,6 @@ info = "معلومات" success = "نجاح" warning = "تحذير" -[portal.infrastructure.certLabel] -certified = "معتمد" -inProgress = "قيد التقدم" -notStarted = "لم يبدأ" - [portal.infrastructure.createKey] cancel = "إلغاء" createKey = "إنشاء مفتاح" @@ -7474,238 +7644,10 @@ subtitleCreated = "انسخ هذا السر الآن — لن يتم عرضه م title = "إنشاء مفتاح API" titleCreated = "تم إنشاء المفتاح" -[portal.infrastructure.deployLabel] -live = "مباشر" -queued = "في قائمة الانتظار" -rolledBack = "تم التراجع" -rolling = "جارٍ النشر" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/دقيقة" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "تم النشر بواسطة" -environment = "البيئة" -product = "المنتج" -status = "الحالة" -version = "الإصدار" -when = "متى" - -[portal.infrastructure.deployments.recent] -heading = "عمليات النشر الأخيرة" -subheading = "أحدث عمليات الطرح عبر المنتجات والبيئات." - -[portal.infrastructure.deployments.regionColumns] -instances = "المثيلات" -latency = "زمن الاستجابة" -load = "الحمل" -p99 = "P99" -region = "المنطقة" -status = "الحالة" -throughput = "الإنتاجية" -uptime = "وقت التشغيل" -version = "الإصدار" - -[portal.infrastructure.deployments.regions] -heading = "المناطق" -subheading = "صحة حية لكل منطقة Stirling منشورة — زمن الاستجابة والحمل وإصدار الطرح." - -[portal.infrastructure.deployments.regions.empty] -description = "ستظهر المناطق المنشورة هنا بمجرد تزويد مساحة عملك." -title = "لا توجد مناطق منشورة" - [portal.infrastructure.keyLabel] active = "نشط" revoked = "مُبطل" -[portal.infrastructure.modelLabel] -active = "نشط" -degraded = "متدهور" -disabled = "معطل" - -[portal.infrastructure.models] -heading = "النماذج" -msValue = "{{value}} ms" -subheading = "كتالوج النماذج والتوجيه الذي يشغل معالجة المستندات عبر مساحة عملك." - -[portal.infrastructure.models.byom] -description = "سجّل نموذجًا داخل المؤسسة أو مستضافًا ذاتيًا وثبّته في منطقة للمعالجة المقيدة بإقامة البيانات." -title = "أحضر نموذجك الخاص" - -[portal.infrastructure.models.catalogue] -heading = "الكتالوج" -sub = "النماذج المُدارة المتاحة لمساحة عملك، مع زمن استجابة وتكلفة حيين." -subEnterprise = "نماذج مُدارة، وأحضر نموذجك الخاص، ونماذج داخل المؤسسة — مع إمكانية التثبيت لكل منطقة." - -[portal.infrastructure.models.catalogue.empty] -description = "ستظهر النماذج في كتالوج مساحة عملك هنا." -title = "لا توجد نماذج متاحة" - -[portal.infrastructure.models.columns] -cost = "التكلفة" -latency = "زمن الاستجابة" -load = "الحمل" -model = "النموذج" -status = "الحالة" -type = "النوع" -version = "الإصدار" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/استدعاء" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "النماذج النشطة" -avgLatency = "متوسط زمن الاستجابة" -included = "مضمن" -monthlySpend = "الإنفاق الشهري على النماذج" - -[portal.infrastructure.models.routing] -empty = "لا توجد قواعد توجيه مكوّنة." -heading = "قواعد التوجيه" -sub = "أي نموذج يتولى كل عملية. ينطبق الافتراضي عندما لا تطابق قاعدة أضيق." -subLocked = "وجّه العمليات إلى نماذج محددة — متاح في الخطط المدفوعة." - -[portal.infrastructure.models.routing.lockedBanner] -description = "قم بالترقية إلى Pro للتحكم في النموذج الذي يتولى كل عملية ونوع مستند." -title = "توجيه النماذج ميزة مدفوعة" - -[portal.infrastructure.models.routingColumns] -default = "افتراضي" -docType = "نوع المستند" -modelForAria = "نموذج لـ {{operation}}" -operation = "العملية" -routedTo = "موجّه إلى" - -[portal.infrastructure.modelTypeLabel] -classification = "التصنيف" -extraction = "الاستخراج" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "متدهور" -down = "متوقف" -healthy = "سليم" - -[portal.infrastructure.security.access.byok] -description = "قدّم مفتاحًا من KMS الخاص بك. يقوم Stirling بالتشفير به لكنه لا يزال قادرًا على القراءة." -label = "أحضر مفتاحك الخاص (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "لا تغادر المفاتيح KMS الخاص بك أبدًا. يحتفظ Stirling بالنص المشفر فقط." -label = "احتفظ بمفتاحك الخاص (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "يدير Stirling مفاتيح التشفير. الأبسط — لا عمليات مفاتيح من جانبك." -label = "مفاتيح يحتفظ بها Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "سياسة الوصول إلى المستندات" -subheading = "تتحكم في من يمكنه فك تشفير المستندات المعالجة أثناء السكون." - -[portal.infrastructure.security.attestations] -heading = "إثباتات الامتثال" -noReport = "لا يوجد تقرير متاح" -subheading = "وضع التدقيق حسب إطار العمل، مع تقارير متاحة للضوابط المثبتة." -viewReport = "عرض التقرير ←" - -[portal.infrastructure.security.compliance] -heading = "الامتثال" -subheading = "إثباتات وشهادات تغطي منصة Stirling." - -[portal.infrastructure.security.empty] -description = "سيظهر تكوين الأمان لمساحة عملك هنا." -title = "وضع الأمان غير متاح" - -[portal.infrastructure.security.hyokBanner] -description = "مع HYOK، لا تغادر مفاتيح التشفير KMS الخاص بك أبدًا. يخزن Stirling ويعالج فقط النص المشفر الذي يمكنك إلغاؤه في أي وقت." -title = "لا يستطيع Stirling فك تشفير مستنداتك" - -[portal.infrastructure.security.ipAllowlist] -empty = "لا توجد نطاقات IP مكوّنة — كل عناوين IP مسموح بها." -heading = "قائمة سماح IP" -sub = "وصول API مقيد بهذه نطاقات CIDR." -subLocked = "قيّد وصول API إلى نطاقات IP معروفة — متاح في الخطط المدفوعة." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "قم بالترقية إلى Pro لتقييد وصول API إلى شبكات محددة." -title = "قائمة سماح IP ميزة مدفوعة" - -[portal.infrastructure.security.ipColumns] -added = "تمت الإضافة" -addedBy = "أضيف بواسطة" -cidr = "CIDR" -label = "التسمية" - -[portal.infrastructure.security.keyManagement] -algorithm = "الخوارزمية" -heading = "إدارة مفاتيح التشفير" -keyId = "معرّف المفتاح" -lastRotated = "آخر تدوير" -rotateKey = "تدوير المفتاح" -rotationPolicy = "سياسة التدوير" -subheading = "حيازة المفاتيح التي تشفر المستندات أثناء السكون — من يمكنه فك التشفير، وكيف يتم تدوير المفاتيح." - -[portal.infrastructure.security.managedBanner] -description = "حيازة أحضر مفتاحك الخاص (BYOK) واحتفظ بمفتاحك الخاص (HYOK) متاحة في Enterprise. قم بالترقية لتقديم مفاتيح من KMS الخاص بك." -title = "يدير Stirling المفاتيح في خطتك" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "آسيا والمحيط الهادئ" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · حدود بيانات GDPR" -label = "الاتحاد الأوروبي" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "الولايات المتحدة" - -[portal.infrastructure.security.residencyHeader] -heading = "إقامة البيانات" -subheading = "مكان تخزين المستندات ومعالجتها." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} مستخدم" - -[portal.infrastructure.storage.empty] -description = "سيظهر التخزين المتصل والاستخدام هنا." -title = "لا يوجد تخزين مكوّن" - -[portal.infrastructure.storage.lifecycle] -active = "نشط" -activeRange = "0–{{value}} يوم" -archived = "مؤرشف" -coldStorage = "تخزين بارد" -deleted = "محذوف" -never = "أبدًا" -purged = "تمت إزالته نهائيًا" - -[portal.infrastructure.storage.providers] -connect = "ربط" -connected = "متصل" -heading = "الموفرون المتصلون" -subheading = "مكان كتابة المخرجات المعالجة." - -[portal.infrastructure.storage.retention] -heading = "الاحتفاظ" -subheading = "مدة الاحتفاظ بالمخرجات قبل حذف دورة الحياة." -windowLabel = "نافذة الاحتفاظ الافتراضية" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} يوم" -days_other = "{{count}} أيام" -never = "عدم الحذف أبدًا" - -[portal.infrastructure.storage.totalUsage] -heading = "إجمالي الاستخدام" -progressLabel = "التخزين المستخدم" -subheading = "التخزين المستهلك عبر جميع الموفرين المتصلين." - [portal.infrastructure.tabs] apiKeys = "مفاتيح API" audit = "سجلات التدقيق" @@ -7715,11 +7657,9 @@ security = "الأمان" storage = "التخزين" [portal.integrations] -addAnother = "إضافة آخر" availableHeading = "متاح" comingSoonHeading = "قريبًا" connect = "اتصال" -connectedHeading = "متصل" connectionCount_one = "{{count}} اتصال" connectionCount_other = "{{count}} اتصالات" customApi = "API مخصص" @@ -7737,6 +7677,10 @@ security = "الأمان" signing = "التوقيع" storage = "التخزين" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "متصل" @@ -7767,7 +7711,6 @@ integrations = "التكاملات" pipelines = "مسارات العمل" policies = "السياسات" procurement = "المشتريات" -settings = "الإعدادات" sources = "المصادر" usage = "الاستخدام والفوترة" users = "المستخدمون" @@ -7784,7 +7727,7 @@ title = "مسارات العمل" newPipeline = "مسار عمل جديد" [portal.pipelines.builder] -addStep = "إضافة أداة" +activate = "Activate" back = "العودة إلى مسارات العمل" cannotFollow = "لا يمكن أخذ {{produced}}" chooseAccount = "اختر حسابًا" @@ -7792,28 +7735,38 @@ chooseDestination = "اختر وجهة" chooseOperation = "اختر ما تفعله هذه الخطوة" chooseSource = "اختر مصدرًا" discard = "تجاهل التغييرات" -enabled = "مفعّل" inputs = "الإدخال" inputSource = "مصدر الإدخال" inputTrigger = "المشغّل" keepEditing = "متابعة التحرير" +moreActions = "More actions" needsConfiguring = "يحتاج إلى إعداد" -needsUpload = "يحتاج إلى ملف مرفوع" -noSources = "لا توجد مصادر متصلة بعد. أنشئ واحدًا لاستخدامه كإدخال." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "لا توجد أدوات تطابق بحثك." -pipelineSettings = "إعدادات مسار العمل" +pause = "Pause" +rename = "Rename pipeline" searchTools = "البحث في الأدوات" -selectToolBody = "أضف أداة لإنشاء مسار عملك." -selectToolTitle = "لا توجد أدوات بعد" sendToSystem = "إرسال إلى نظام آخر" stepsIncompatible = "لا يمكن تشغيل هذه الخطوات على ما تنتجه الخطوة السابقة: {{tools}}." stepsNeedSetup = "لا تزال هذه الخطوات بحاجة إلى إعداد قبل الحفظ: {{tools}}." -toolSettings = "إعدادات الأداة" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "عملية غير معروفة، تم الإبقاء عليها كما هي." unsavedBody = "لديك تغييرات غير محفوظة. احفظها قبل المغادرة، أم تتجاهلها؟" unsavedTitle = "تغييرات غير محفوظة" -uploadUnsupported = "الملفات المرفوعة غير مدعومة في مسارات العمل بعد، لذلك لا يمكن حفظ هذه الخطوات: {{tools}}." usesDefaults = "يعمل بالإعدادات الافتراضية" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "يجمع كل ملف من الخطوة السابقة" @@ -7825,31 +7778,36 @@ undeclared-operation = "لا يمكن التحقق مما تقبله هذه ال [portal.pipelines.composer] addTool = "إضافة أداة" -cancel = "إلغاء" -chainEmpty = "أضف أداة لبدء إنشاء مسار عملك." create = "إنشاء مسار عمل" +createPaused = "Create paused" editingUnsupported = "عرض معاملات هذه الأداة للتحرير غير مدعوم بعد." -moveDown = "نقل إلى الأسفل" -moveUp = "نقل إلى الأعلى" +editSource = "Edit source" name = "الاسم" namePlaceholder = "مثال: مسح التنقيح" noToolSettings = "لا تحتوي هذه الأداة على إعدادات قابلة للتكوين." -operations_one = "عملية ({{count}})" -operations_other = "عمليات ({{count}})" output = "الإخراج" -removeStep = "إزالة العملية" save = "حفظ التغييرات" scheduleEvery = "تشغيل كل" -sources = "المصادر" -sourcesLoading = "جارٍ تحميل المصادر..." trigger = "المشغّل" triggerManual = "يدوي فقط" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "أيام" hours = "ساعات" minutes = "دقائق" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "حذف \"{{name}}\"؟ لا يمكن التراجع عن هذا." cancel = "إلغاء" @@ -7867,6 +7825,37 @@ connectSource = "اتصال بمصدر" description = "أنشئ مسار عملك الأول: اختر المصادر التي يعمل عليها، وسلسلة العمليات، ومكان انتقال الإخراج." title = "لا توجد مسارات عمل بعد" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "نشطة" paused = "متوقفة مؤقتًا" @@ -7879,7 +7868,6 @@ completed_one = "اكتمل التشغيل." completed_other = "اكتملت كل عمليات التشغيل البالغ عددها {{count}}." empty = "لا يوجد شيء لتشغيله: لم تكن لدى المصادر مستندات للمعالجة." failed = "فشل التشغيل: {{error}}" -historyCleared = "تم مسح السجل. ستعيد عملية التشغيل التالية معالجة كل ما هو موجود حاليًا في المصادر." inFlight = "لا يوجد شيء جديد لتشغيله: لا تزال المستندات قيد المعالجة من عملية تشغيل سابقة." parked_one = "لا يوجد ما يمكن تشغيله: فشل مستند {{count}} سابقًا وتم إيقافه. أصلح السبب، ثم امسح السجل لإعادة المحاولة." parked_other = "لا يوجد ما يمكن تشغيله: فشلت مستندات عددها {{count}} سابقًا وتم إيقافها. أصلح السبب، ثم امسح السجل لإعادة المحاولة." @@ -7895,6 +7883,7 @@ name = "مسار العمل" sources = "المصادر" status = "الحالة" steps = "الخطوات" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "مراقبة المجلد" @@ -8209,6 +8198,24 @@ label = "إرفاق بمشكلة Jira" description = "يرسل المستند المعالج كمرفق." label = "إرسال المستند بالبريد الإلكتروني (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "يكتب المستند المعالج في مجلد." label = "رفع إلى Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "بلغ الحد الأقصى" subtitle = "تستمر تجربتك المجانية حتى {{date}}. لا حاجة إلى بطاقة." title = "تجربة Enterprise" -[portal.search] -ariaLabel = "بحث" -placeholder = "ابحث في Stirling — endpoints، المسارات، المستندات…" - -[portal.search.empty] -noActionsDescription = "ستظهر الإجراءات السريعة هنا بمجرد توفرها." -noActionsTitle = "لا توجد إجراءات سريعة" -noMatches = "لا توجد نتائج لـ \"{{query}}\"" -noMatchesDescription = "جرّب كلمة مفتاحية مختلفة أو تصفح الفهرس." - [portal.settings.groups] admin = "المسؤول" @@ -8743,10 +8740,6 @@ cancel = "إلغاء" confirm = "حذف" title = "حذف المصدر؟" -[portal.sources.empty] -description = "وصّل مجلدًا (وقريبًا، التخزين السحابي) حتى يكون لدى سياساتك مكان تسحب منه المستندات." -title = "لا توجد مصادر متصلة بعد" - [portal.sources.kpi] inUse = "قيد الاستخدام" total = "الاتصالات" @@ -8785,6 +8778,7 @@ unused = "غير مستخدم" documents = "المستندات" source = "المصدر" status = "الحالة" +type = "Type" usedBy = "السياسات" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "السياسات والخصوصية" [settings.preferences] title = "التفضيلات" -[settings.search] -placeholder = "ابحث في صفحات الإعدادات..." - [settings.security] description = "حدّث كلمة مرورك للحفاظ على أمان حسابك." title = "الأمان" @@ -10128,6 +10119,7 @@ last = "الصفحة الأخيرة" maintainRatio = "تبديل الحفاظ على نسبة الأبعاد" next = "الصفحة التالية" noSavedSigs = "لم يتم العثور على توقيعات محفوظة" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "توقيعات شخصية" previous = "الصفحة السابقة" redo = "إعادة" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "فشل إزالة الخلفية" hint = "ارفع صورة PNG أو JPG لتوقيعك" label = "رفع صورة التوقيع" placeholder = "اختر ملف صورة" +previewAlt = "Current image signature" processing = "جارٍ معالجة الصورة..." removeBackground = "إزالة الخلفية البيضاء (جعلها شفافة)" @@ -10179,6 +10172,17 @@ saved = "اختر توقيعاً محفوظاً أعلاه، ثم انقر في text = "بعد إدخال اسمك أعلاه، انقر في أي مكان على ملف PDF لوضع توقيعك." title = "كيفية إضافة توقيع" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "تحريك التوقيع" pause = "إيقاف الوضع مؤقتاً" @@ -10625,6 +10629,23 @@ title = "التحميل إلى الخادم" updateButton = "تحديث على الخادم" uploadButton = "تحميل إلى الخادم" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "رجوع" pdfTools = "أدوات PDF" placeholder = "اختر أداة للبدء" premiumFeature = "ميزة Premium:" -searchTools = "البحث في الأدوات" toolsHeader = "الأدوات" viewAllTools = "عرض كل الأدوات" @@ -10822,6 +10842,9 @@ removal = "إزالة" signing = "توقيع" verification = "تحقق" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "لم يتم العثور على أدوات" noTools = "لا توجد أدوات متاحة" @@ -10979,6 +11002,13 @@ approver = "يعتمد السياسة" editor = "المحرر" processor = "المعالج" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "إلغاء الدعوة إلى {{email}}؟ لن يتمكنوا من الانضمام باستخدام الرابط الحالي." cancelInviteTitle = "إلغاء الدعوة" @@ -10998,10 +11028,8 @@ title = "لا يوجد أعضاء بعد" addToTeam = "إضافة إلى فريق" guestCount = "{{count}} ضيف" guests = "الضيوف" -guestsDesc = "متعاونون خارجيون، ضمن نطاق ما شاركته. المحرر فقط." ledBy = "بقيادة {{owner}}" org = "المؤسسة" -orgDesc = "مالكون بسلطة على مستوى المؤسسة واعتماد السياسات" owners = "{{count}} مالك" team = "فريق {{name}}" teamMeta = "{{count}} شخص" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "تمت الدعوة بواسطة {{who}}" cancel = "إلغاء" -count = "{{count}} معلقة" -desc = "الأشخاص المدعوون الذين لم ينضموا بعد. يحتفظون بمقعد حتى يقبلوا." expiresInDays_one = "تنتهي خلال يوم واحد" expiresInDays_other = "تنتهي خلال {{count}} أيام" expiresToday = "تنتهي اليوم" title = "الدعوات المعلقة" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "حدث خطأ ما أثناء الوصول إلى الخادم الخلفي، أو ليس لديك صلاحية الوصول. حاول مرة أخرى." title = "تعذر تحميل الأعضاء" @@ -11237,12 +11267,15 @@ searchPlaceholder = "ابحث في المرفقات" title = "المرفقات" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "عنوان الإشارة المرجعية" bookmarkTitleRequired = "عنوان الإشارة المرجعية مطلوب" closeSidebar = "إغلاق الشريط الجانبي للإشارات المرجعية" collapseAll = "طي جميع الإشارات المرجعية" +empty = "No bookmarks in this document" expandAll = "توسيع جميع الإشارات المرجعية" searchPlaceholder = "البحث في الإشارات المرجعية" +title = "Bookmarks" [viewer.comments] addComment = "إضافة تعليق" @@ -11264,10 +11297,12 @@ locateAnnotation = "تحديد الموقع في المستند" moreActions = "إجراءات أخرى" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "الصفحة {{page}}" placingHint = "انقر على صفحة لوضع… (إلغاء)" removeCommentOnly = "إزالة التعليق فقط" saveReply = "حفظ الرد" +searchPlaceholder = "Search comments" title = "التعليقات" typeComment = "تعليق" typeInsertText = "إدراج نص" @@ -11290,6 +11325,7 @@ unsavedDesc = "لديك تغييرات غير محفوظة" closeSidebar = "إغلاق الشريط الجانبي للطبقات" hideAll = "إخفاء جميع الطبقات" showAll = "إظهار جميع الطبقات" +title = "Layers" [viewer.link] delete = "حذف الرابط" @@ -11646,6 +11682,7 @@ exitRedaction = "إنهاء وضع التنقيح" exportAll = "تصدير PDF" exportSelected = "تصدير الصفحات المحددة" formFill = "تعبئة النموذج" +hideToolbar = "Hide toolbar" multiTool = "الأداة المتعددة" panMode = "وضع التحريك" print = "طباعة PDF" @@ -11666,6 +11703,7 @@ selectAll = "تحديد الكل" selectByNumber = "تحديد حسب أرقام الصفحات" selectLanguage = "اختر اللغة" share = "مشاركة" +showToolbar = "Show toolbar" toggleAnnotations = "تبديل ظهور الشروح" toggleAttachments = "تبديل عرض المرفقات" toggleBookmarks = "تبديل الإشارات المرجعية" diff --git a/frontend/editor/public/locales/az-AZ/translation.toml b/frontend/editor/public/locales/az-AZ/translation.toml index 9df31ec5b0..c89316720f 100644 --- a/frontend/editor/public/locales/az-AZ/translation.toml +++ b/frontend/editor/public/locales/az-AZ/translation.toml @@ -1,7 +1,6 @@ alphabet = "Əlifba" apply = "Tətbiq et" applyAndContinue = "Saxla və çıx" -areYouSure = "Çıxmaq istədiyinizə əminsiniz?" back = "Geri" black = "Qara" blue = "Mavi" @@ -106,6 +105,7 @@ undoSuccess = "Əməliyyat uğurla geri alındı" unknown = "Bilinməyən" unpin = "Faylın bərkidilməsini ləğv et (alət icrasından sonra əvəzlə)" unsavedChanges = "PDF-də saxlanmamış dəyişiklikləriniz var." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Saxlanmamış dəyişikliklər" unsupported = "Dəstəklənmir" username = "İstifadəçi Adı" @@ -1879,6 +1879,9 @@ width = "Genişlik" [app] description = "Pulsuz Adobe Acrobat alternativi (10M+ yükləmə)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "PDF/A-3b-ə çevir" convertToPdfA3bDescription = "Daxil edilmiş qoşmalarla arxivləmə üçün PDF yaradır" @@ -2933,12 +2936,16 @@ text = "Bu icazələri dəyişdirilməz etmək üçün, Parol əlavə etmə alə [chat.actions] copy = "Mesajı kopyala" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling agent seçimləri" +assistant = "AI Assistant" clearChat = "Söhbəti təmizlə" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Bu əməliyyat bulud kreditlərinizi istifadə edəcək" tooltip = "Ekrandan rəng seç" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Rəng seçin" [common] +actions = "Actions" back = "Geri" cancel = "Ləğv et" close = "Bağla" +codeSample = "Code sample" collapse = "Yığ" confirm = "Təsdiqlə" continue = "Davam et" copied = "Kopyalandı!" copy = "Kopyala" done = "Bitdi" +enterValue = "Enter value" error = "Xəta" expand = "Genişləndir" loading = "Yüklənir..." next = "Növbəti" +open = "Open" preview = "Önizləmə" previous = "Əvvəlki" refresh = "Yenilə" remaining = "Qalıb" retry = "Yenidən cəhd et" save = "Yadda saxla" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Seçilənləri təmizlə" @@ -3185,6 +3199,7 @@ title = "Sıxma metodu" [compress.settings] desiredSize = "İstənilən fayl ölçüsü" desiredSizePlaceholder = "Ölçünü daxil edin" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Sıxma fayl ölçüsünü azaltmağın asan yoludur. 'Fayl ölçüsü'nü seçərək hədəf ölçünü daxil edin və keyfiyyəti sizin üçün tənzimləyək. 'Keyfiyyət'i seçərək sıxma gücünü əl ilə təyin edin." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF seçimləri" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Əvvəlcə mənbə formatını seçin" settings = "Parametrlər" single = "Tək" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Axtar..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Redaktə et" @@ -3824,11 +3861,13 @@ shareSelected = "Seçilənləri paylaş" sharing = "Paylaşma" showAll = "Hamısını göstər" showHistory = "Tarixçəni göstər" +sortBy = "Sort files" sortByDate = "Tarixə görə sırala" sortByName = "Ada görə sırala" sortBySize = "Ölçüyə görə sırala" storage = "Saxlama" storageState = "Saxlama" +storageUsed = "Storage used" synced = "Sinxronlaşdırılıb" title = "PDF Fayllarını Yüklə" toolChain = "Tətbiq olunan alətlər" @@ -3874,8 +3913,12 @@ addFiles = "Fayllar əlavə et" addingFiles = "Fayllar əlavə edilir…" collapse = "Yan paneli yığ" customizeGroups = "Qrupları fərdiləşdir" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Başlamaq üçün faylları açın" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Yan paneli genişləndir" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive konfiqurasiya edilməyib" @@ -3888,15 +3931,17 @@ openFromComputer = "Kompüterdən aç" openSettings = "Ayarları aç" other = "Digər" recent = "Son" -search = "Axtar" -searchPlaceholder = "Fayllarda axtar..." viewAll = "Bütün {{count}} faylı göstər" [fileSidebar.fileItem] closeViewer = "Görüntüləyicini bağla" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Görüntüləyicidə aç" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Defoltlara sıfırla" subtitle = "Fayllarınızı əsas kateqoriyalara qruplaşdırın. Kateqoriyaya mövcud və ya yeni etiketlər əlavə edin, adını dəyişin və ya özünüz yaradın. Kateqoriyalarınızdan heç birində olmayan fayllar “Digər” altında görünür." title = "Yan panel kateqoriyaları" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "İş sahəsinə əlavə et" addToWorkspaceCount = "{{count}} faylı iş sahəsinə əlavə et" @@ -3918,7 +3971,6 @@ backToMyFiles = "Mənim Fayllarıma geri" breadcrumbs = "Qovluq yolu" cancel = "Ləğv et" classification = "Təsnifat" -clearSearch = "Axtarışı təmizlə" clearSelection = "Seçimi təmizlə" closeDetails = "Təfərrüatları bağla" create = "Yarat" @@ -3950,6 +4002,7 @@ downloadAll = "Hamısını endir" downloadVersion = "Bu versiyanı endir" dropOverlay = "Yükləmək üçün faylları buraxın" dropOverlaySub = "Fayllar əvvəlcə Lokal bölmədə başlayır. Onları qovluğa yerləşdirmək üçün 'Köçür' və ya 'Buluda saxla'dan istifadə edin." +duplicate = "Duplicate" file = "Fayl" fileInfo = "File info" fileMenu = "Fayl əməliyyatları" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Qovluq ağacının ölçüsünü dəyiş (ox düymələri, d save = "Saxla" saveToServer = "Serverə saxla" saveToServerDisabledHint = "Bu serverdə serverə saxlama aktiv deyil. Administratorunuzdan bunu aktivləşdirməsini xahiş edin." -search = "Axtar" -searchPlaceholder = "Bu qovluqda və alt qovluqlarda axtar" selectAll = "Hamısını seç" selectAllHint = "Hamısını seçmək üçün klikləyin. Məsləhət: faylları tək-tək əlavə etmək üçün Ctrl (və ya Cmd), aralığı seçmək üçün Shift basın." selectedCount = "{{count}} seçilib" @@ -4044,6 +4095,10 @@ title = "Hələ bulud faylı yoxdur" hint = "Yüklənmədən saxlanılan fayllar burada qalır. Əlavə etmək üçün faylı buraxın." title = "Yalnız lokal fayl yoxdur" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Açdığınız və ya redaktə etdiyiniz fayllar burada görünəcək." title = "Hələ heç nə dəyişdirilməyib" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Qovluğu silmək mümkün olmadı." deleteFolderFailedDetail = "Qovluğu silmək mümkün olmadı: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Qovluğun görünüşünü yeniləmək mümkün olmadı." folderAppearanceFailedDetail = "Qovluğun görünüşünü yeniləmək mümkün olmadı: {{message}}" moveFilesFailed = "Faylları köçürmək mümkün olmadı." @@ -4113,6 +4170,11 @@ localHint = "Yalnız bu brauzerdə saxlanılır" shared = "Paylaşılan" sharedHint = "Keçid vasitəsilə sizinlə paylaşılıb" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Ən köhnə əvvəlcə" modifiedDesc = "Ən yenilər əvvəlcə" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Yaddaşda fayl yoxdur. Əvvəlcə bəzi faylları açın open = "Aç" openFile = "Faylı aç" openFiles = "Faylları aç" +selectFile = "Select {{name}}" selectFromStorage = "Saxlamadan seç" upload = "Yüklə" uploadFile = "Fayl yüklə" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Hamısı saxlanıldı" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV çıxarmaq alınmadı" extractXlsxError = "XLSX çıxarmaq alınmadı" +filled = "filled" flattenAfterFilling = "Doldurduqdan sonra yastılaşdır" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "təl." requiredFieldsError = "Bütün tələb olunan sahələri doldurun" rescanFields = "Sahələri yenidən skan et" @@ -4795,9 +4863,6 @@ title = "Şəkli PDF-ə" [imageToPdf] tags = "çevirmə,şəkil,jpg,fotoşəkil,foto" -[infoBanner] -dismiss = "Bağla" - [invite] acceptError = "Hesabın yaradılması alınmadı" accountFor = "üçün hesab yaradılır" @@ -4979,6 +5044,34 @@ uploadSuccess = "Uğurla yükləndi!" uploadSuccessMessage = "Şəkilləriniz köçürüldü." validating = "Sessiya yoxlanılır..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Skan edib fotoları yükləyin. Şəkillər avtomatik olaraq PDF-ə çevrilir." descriptionNoConvert = "Mobil cihazınızdan fotoları yükləmək üçün skan edin." @@ -4997,8 +5090,13 @@ title = "Mobil cihazdan yüklə" tags = "Multi-alət,Çoxlu əməliyyat,UI,tut-sürüşdür,front end,istifadəçi-tərəf,interaktiv,qarşılıqlı,yerini dəyiş,sil,köçür,böl" title = "PDF Multi-Alət" -[navbar] -search = "Axtar" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Təsdiqləmə uğurlu olmadı. Bu pəncərəni bağlayıb yenidən cəhd edə bilərsiniz." @@ -5127,6 +5225,7 @@ activeFiles = "Aktiv Fayllar görünüşü alətə yüklədiyin allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Bağla" cropSettings = "Artıq kəsmək istədiyimiz faylı seçdiyimizə görə, PDF-i kəsmək istədiyimiz sahəni seçmək üçün Kəsmə alətini tənzimləyə bilərik." +dialogLabel = "Onboarding" fileCheckbox = "Fayllardan birinə klikləmək onu emal üçün seçir. Toplu əməliyyatlar üçün bir neçə faylı seçə bilərsiniz." fileReplacement = "Dəyişdirilmiş fayl İş Masasında orijinal faylı avtomatik əvəz edəcək və onu asanlıqla daha çox alətdən keçirməyə imkan verəcək." filesButton = "Sürətli Giriş panelindəki Fayllar düyməsi PDF-ləri alətlərdə istifadə etmək üçün yükləməyə imkan verir." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF-i PDF/A-ya" tags = "arxiv,uzunmüddətli,standard,çevirmə,yaddaş,saxlama" title = "PDF-i PDF/A-ya" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "çap,standart,çevrilmə,istehsal,prepress,arxiv" title = "PDF-dən PDF/X-ə" @@ -6045,6 +6151,7 @@ small = "500 Credits" xsmall = "100 Credits" [plan.availablePlans] +currency = "Billing currency" subtitle = "Ehtiyaclarınıza uyğun planı seçin" title = "Mövcud Planlar" @@ -6624,6 +6731,7 @@ reachedTitle = "Aylıq xərc limitinə çatıldı" title = "Stripe portalını açmaq mümkün olmadı" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "{{allowance}} pulsuz PDF-dən istifadə olunub" capSuffix_other = "{{allowance}} pulsuz PDF-dən istifadə olunub" eyebrow = "Processor sınağı" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Siyasət işlədikdə sənəd və ya bildirişi email ilə göndərin." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Emal edilmiş sənədləri Nextcloud-a yerləşdirin." @@ -7077,11 +7211,6 @@ title = "Rəsmi SDK-lar" beta = "Beta" deprecated = "Köhnəlmiş" -[portal.docs.search] -empty = "Uyğun sənəd yoxdur" -placeholder = "Sənədlərdə axtar" -results = "{{count}} nəticə" - [portal.docs.skills] eyebrow = "BACARIQLAR" lead = "Agentinizin tək alət kimi çağırdığı paketlənmiş, adlandırılmış imkanlar. Hər bacarıq əlavə edilmiş qiymətləndirmələri olan deterministik əməliyyat zənciridir." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Bu filtrə uyğun sənəd yoxdur." rowActions = "Sətir əməliyyatları" sensitiveLabel = "Həssas" -sensitiveTitle = "Həssas — giriş tələb olunur" [portal.documents.table.columns] action = "Pipeline / Əməliyyat" document = "Sənəd" +labels = "Labels" product = "Məhsul" status = "Status" time = "Vaxt" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Son görünmə" region = "Region" status = "Status" +target = "Target" version = "Versiya" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Bu görünüşdə gözlənilməz xəta baş verdi. Yenidən cəhd retry = "Yenidən cəhd et" title = "Bu səhifədə nəsə səhv getdi" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Geri" body = "Desktop tətbiqini yükləyin və ya serveri self-host edin." @@ -7380,11 +7560,6 @@ cancel = "Ləğv et" confirm = "Açarı ləğv et" title = "API açarını ləğv et" -[portal.infrastructure.attestationLabel] -attested = "Təsdiqlənib" -inScope = "Əhatə dairəsində" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Audit hadisələrini kateqoriyaya görə filtrlə" heading = "Audit jurnalları" @@ -7456,11 +7631,6 @@ info = "Məlumat" success = "Uğur" warning = "Xəbərdarlıq" -[portal.infrastructure.certLabel] -certified = "Sertifikatlaşdırılıb" -inProgress = "Davam edir" -notStarted = "Başlamayıb" - [portal.infrastructure.createKey] cancel = "Ləğv et" createKey = "Açar yarat" @@ -7474,238 +7644,10 @@ subtitleCreated = "Bu sirri indi kopyalayın — bir daha göstərilməyəcək." title = "API açarı yarat" titleCreated = "Açar yaradıldı" -[portal.infrastructure.deployLabel] -live = "Canlı" -queued = "Növbədə" -rolledBack = "Geri qaytarılıb" -rolling = "Yayılır" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/dəq" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Yerləşdirən" -environment = "Mühit" -product = "Məhsul" -status = "Status" -version = "Versiya" -when = "Nə vaxt" - -[portal.infrastructure.deployments.recent] -heading = "Son yerləşdirmələr" -subheading = "Məhsullar və mühitlər üzrə ən son rollout-lar." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instansiyalar" -latency = "Gecikmə" -load = "Yük" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Ötürücülük" -uptime = "İşləmə vaxtı" -version = "Versiya" - -[portal.infrastructure.deployments.regions] -heading = "Regionlar" -subheading = "Hər yerləşdirilmiş Stirling regionu üçün canlı sağlamlıq — gecikmə, yük və rollout versiyası." - -[portal.infrastructure.deployments.regions.empty] -description = "İş sahəniz təmin edildikdən sonra yerləşdirilmiş regionlar burada görünəcək." -title = "Yerləşdirilmiş region yoxdur" - [portal.infrastructure.keyLabel] active = "Aktiv" revoked = "Ləğv edilib" -[portal.infrastructure.modelLabel] -active = "Aktiv" -degraded = "Zəifləmiş" -disabled = "Deaktiv edilib" - -[portal.infrastructure.models] -heading = "Modellər" -msValue = "{{value}} ms" -subheading = "İş sahəniz üzrə sənəd emalını təmin edən model kataloqu və yönləndirmə." - -[portal.infrastructure.models.byom] -description = "On-prem və ya öz hostinqinizdə olan modeli qeydiyyatdan keçirin və data residency ilə bağlı emal üçün onu regiona bərkidin." -title = "Öz modelinizi gətirin" - -[portal.infrastructure.models.catalogue] -heading = "Kataloq" -sub = "İş sahəniz üçün əlçatan idarə olunan modellər, canlı gecikmə və xərclə." -subEnterprise = "İdarə olunan, özünüzün gətirdiyiniz və on-prem modellər — region üzrə bərkitmə imkanı ilə." - -[portal.infrastructure.models.catalogue.empty] -description = "İş sahənizin kataloqundakı modellər burada görünür." -title = "Əlçatan model yoxdur" - -[portal.infrastructure.models.columns] -cost = "Xərc" -latency = "Gecikmə" -load = "Yük" -model = "Model" -status = "Status" -type = "Növ" -version = "Versiya" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/zəng" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktiv modellər" -avgLatency = "Orta gecikmə" -included = "Daxildir" -monthlySpend = "Aylıq model xərci" - -[portal.infrastructure.models.routing] -empty = "Yönləndirmə qaydası konfiqurasiya edilməyib." -heading = "Yönləndirmə qaydaları" -sub = "Hər əməliyyatı hansı modelin idarə etdiyi. Daha dar qayda uyğun gəlmədikdə standart tətbiq olunur." -subLocked = "Əməliyyatları konkret modellərə yönləndirin — ödənişli planlarda əlçatandır." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Hər əməliyyatı və sənəd növünü hansı modelin idarə etdiyinə nəzarət etmək üçün Pro-ya yüksəldin." -title = "Model yönləndirməsi ödənişli funksiyadır" - -[portal.infrastructure.models.routingColumns] -default = "Standart" -docType = "Sənəd növü" -modelForAria = "{{operation}} üçün model" -operation = "Əməliyyat" -routedTo = "Yönləndirilib" - -[portal.infrastructure.modelTypeLabel] -classification = "Təsnifat" -extraction = "Çıxarma" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Zəifləmiş" -down = "İşləmir" -healthy = "Sağlam" - -[portal.infrastructure.security.access.byok] -description = "Öz KMS-inizdən açar təqdim edin. Stirling onunla şifrələyir, lakin yenə də oxuya bilir." -label = "Öz açarınızı gətirin (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Açarlar KMS-inizi heç vaxt tərk etmir. Stirling yalnız ciphertext saxlayır." -label = "Öz açarınızı saxlayın (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling şifrələmə açarlarını idarə edir. Ən sadə variant — sizin tərəfdə sıfır açar əməliyyatı." -label = "Stirling-də saxlanılan açarlar" - -[portal.infrastructure.security.accessPolicy] -heading = "Sənəd giriş siyasəti" -subheading = "Emal edilmiş sənədləri saxlanma vəziyyətində kimin deşifr edə biləcəyinə nəzarət edir." - -[portal.infrastructure.security.attestations] -heading = "Uyğunluq attestatları" -noReport = "Hesabat əlçatan deyil" -subheading = "Attestasiya edilmiş nəzarətlər üzrə hesabatlarla framework üzrə audit vəziyyəti." -viewReport = "Hesabata bax →" - -[portal.infrastructure.security.compliance] -heading = "Uyğunluq" -subheading = "Stirling platformasını əhatə edən attestatlar və sertifikatlar." - -[portal.infrastructure.security.empty] -description = "İş sahənizin təhlükəsizlik konfiqurasiyası burada görünəcək." -title = "Təhlükəsizlik vəziyyəti əlçatan deyil" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK ilə şifrələmə açarları KMS-inizi heç vaxt tərk etmir. Stirling yalnız istənilən vaxt ləğv edə biləcəyiniz ciphertext-i saxlayır və emal edir." -title = "Stirling sənədlərinizi deşifr edə bilmir" - -[portal.infrastructure.security.ipAllowlist] -empty = "IP aralığı konfiqurasiya edilməyib — bütün IP-lərə icazə verilir." -heading = "IP allowlist" -sub = "API girişi bu CIDR aralıqları ilə məhdudlaşdırılıb." -subLocked = "API girişini tanınmış IP aralıqları ilə məhdudlaşdırın — ödənişli planlarda əlçatandır." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "API girişini konkret şəbəkələrlə məhdudlaşdırmaq üçün Pro-ya yüksəldin." -title = "IP allowlisting ödənişli funksiyadır" - -[portal.infrastructure.security.ipColumns] -added = "Əlavə edilib" -addedBy = "Əlavə edən" -cidr = "CIDR" -label = "Etiket" - -[portal.infrastructure.security.keyManagement] -algorithm = "Alqoritm" -heading = "Şifrələmə açarlarının idarə edilməsi" -keyId = "Açar identifikatoru" -lastRotated = "Son dövr edilmə" -rotateKey = "Açarı dövr et" -rotationPolicy = "Dövr etmə siyasəti" -subheading = "Sənədləri saxlanma vəziyyətində şifrələyən açarların saxlanması — kim deşifr edə bilər və açarlar necə dövr edilir." - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key (BYOK) və hold-your-own-key (HYOK) saxlanması Enterprise-də əlçatandır. Öz KMS-inizdən açarlar təqdim etmək üçün yüksəldin." -title = "Planınızda açarlar Stirling tərəfindən idarə olunur" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asiya-Sakit okean" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR data boundary" -label = "Avropa İttifaqı" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Amerika Birləşmiş Ştatları" - -[portal.infrastructure.security.residencyHeader] -heading = "Data residency" -subheading = "Sənədlərin saxlandığı və emal edildiyi yer." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} istifadə edilib" - -[portal.infrastructure.storage.empty] -description = "Qoşulmuş saxlama və istifadə burada görünəcək." -title = "Saxlama konfiqurasiya edilməyib" - -[portal.infrastructure.storage.lifecycle] -active = "Aktiv" -activeRange = "0–{{value}}g" -archived = "Arxivləşdirilib" -coldStorage = "cold storage" -deleted = "Silinib" -never = "heç vaxt" -purged = "təmizlənib" - -[portal.infrastructure.storage.providers] -connect = "Qoş" -connected = "Qoşulub" -heading = "Qoşulmuş provayderlər" -subheading = "Emal edilmiş artefaktların yazıldığı yer." - -[portal.infrastructure.storage.retention] -heading = "Saxlama müddəti" -subheading = "Artefaktların lifecycle silinməsindən əvvəl nə qədər saxlanıldığı." -windowLabel = "Standart saxlama pəncərəsi" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} gün" -days_other = "{{count}} gün" -never = "Heç vaxt silmə" - -[portal.infrastructure.storage.totalUsage] -heading = "Ümumi istifadə" -progressLabel = "İstifadə edilmiş saxlama" -subheading = "Bütün qoşulmuş provayderlər üzrə istifadə edilmiş saxlama." - [portal.infrastructure.tabs] apiKeys = "API açarları" audit = "Audit jurnalları" @@ -7715,11 +7657,9 @@ security = "Təhlükəsizlik" storage = "Saxlama" [portal.integrations] -addAnother = "Başqasını əlavə et" availableHeading = "Mövcud" comingSoonHeading = "Tezliklə" connect = "Qoşul" -connectedHeading = "Qoşulub" connectionCount_one = "{{count}} bağlantı" connectionCount_other = "{{count}} bağlantı" customApi = "Xüsusi API" @@ -7737,6 +7677,10 @@ security = "Təhlükəsizlik" signing = "İmzalama" storage = "Yaddaş" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Qoşulub" @@ -7767,7 +7711,6 @@ integrations = "İnteqrasiyalar" pipelines = "Pipeline-lar" policies = "Siyasətlər" procurement = "Satınalma" -settings = "Parametrlər" sources = "Mənbələr" usage = "İstifadə və fakturalama" users = "İstifadəçilər" @@ -7784,7 +7727,7 @@ title = "Pipeline-lar" newPipeline = "Yeni pipeline" [portal.pipelines.builder] -addStep = "Alət əlavə et" +activate = "Activate" back = "Pipeline-lara qayıt" cannotFollow = "{{produced}} qəbul edə bilmir" chooseAccount = "Hesab seçin" @@ -7792,28 +7735,38 @@ chooseDestination = "Təyinat seçin" chooseOperation = "Bu addımın nə edəcəyini seçin" chooseSource = "Mənbə seçin" discard = "Dəyişiklikləri at" -enabled = "Aktivdir" inputs = "Giriş" inputSource = "Giriş mənbəyi" inputTrigger = "Trigger" keepEditing = "Redaktəyə davam et" +moreActions = "More actions" needsConfiguring = "Quraşdırma tələb olunur" -needsUpload = "Yüklənmiş fayl tələb olunur" -noSources = "Hələ heç bir mənbə qoşulmayıb. Onu giriş kimi istifadə etmək üçün birini yaradın." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Axtarışınıza uyğun alət yoxdur." -pipelineSettings = "Pipeline parametrləri" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Alətləri axtar" -selectToolBody = "Pipeline qurmaq üçün alət əlavə edin." -selectToolTitle = "Hələ alət yoxdur" sendToSystem = "Başqa sistemə göndər" stepsIncompatible = "Bu addımlar əvvəlki addımın yaratdığı ilə işləyə bilmir: {{tools}}." stepsNeedSetup = "Saxlamadan əvvəl bu addımların hələ quraşdırılması lazımdır: {{tools}}." -toolSettings = "Alət parametrləri" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Tanınmayan əməliyyat, olduğu kimi saxlanıldı." unsavedBody = "Saxlanmamış dəyişiklikləriniz var. Çıxmadan əvvəl onları saxlayırsınız, yoxsa atırsınız?" unsavedTitle = "Saxlanmamış dəyişikliklər" -uploadUnsupported = "Yüklənmiş fayllar hələ pipeline-larda dəstəklənmir, buna görə bu addımlar saxlanıla bilməz: {{tools}}." usesDefaults = "Standart parametrlərlə işləyir" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Əvvəlki addımdan bütün faylları birləşdirir" @@ -7825,31 +7778,36 @@ undeclared-operation = "Bu addımın nə qəbul etdiyini yoxlamaq olmur" [portal.pipelines.composer] addTool = "Alət əlavə et" -cancel = "Ləğv et" -chainEmpty = "Pipeline qurmağa başlamaq üçün alət əlavə edin." create = "Pipeline yarat" +createPaused = "Create paused" editingUnsupported = "Bu alət parametrlərinin redaktə üçün göstərilməsi hələ dəstəklənmir." -moveDown = "Aşağı köçür" -moveUp = "Yuxarı köçür" +editSource = "Edit source" name = "Ad" namePlaceholder = "məs. Redaction sweep" noToolSettings = "Bu alətin konfiqurasiya edilə bilən parametri yoxdur." -operations_one = "Əməliyyat ({{count}})" -operations_other = "Əməliyyatlar ({{count}})" output = "Çıxış" -removeStep = "Əməliyyatı sil" save = "Dəyişiklikləri saxla" scheduleEvery = "Hər" -sources = "Mənbələr" -sourcesLoading = "Mənbələr yüklənir..." trigger = "Trigger" triggerManual = "Yalnız manual" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "gün" hours = "saat" minutes = "dəqiqə" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\" pipeline-ı silinsin? Bu əməliyyat geri qaytarıla bilməz." cancel = "Ləğv et" @@ -7867,6 +7825,37 @@ connectSource = "Mənbə qoş" description = "İlk pipeline-ınızı yaradın: işləyəcəyi mənbələri seçin, əməliyyatları zəncirləyin və çıxışın hara gedəcəyini seçin." title = "Hələ pipeline yoxdur" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktiv" paused = "Dayandırılıb" @@ -7879,7 +7868,6 @@ completed_one = "İşlətmə tamamlandı." completed_other = "Bütün {{count}} işlətmə tamamlandı." empty = "İşlətmək üçün heç nə yoxdur: mənbələrdə emal ediləcək sənəd yox idi." failed = "İşlətmə alınmadı: {{error}}" -historyCleared = "Tarixçə təmizləndi. Növbəti işə salma hazırda mənbələrdə olan hər şeyi yenidən emal edir." inFlight = "İşlədiləcək yeni heç nə yoxdur: sənədlər əvvəlki işə salmadan hələ də emal olunur." parked_one = "İşlədiləcək heç nə yoxdur: {{count}} sənəd əvvəl uğursuz olub və gözləməyə qoyulub. Səbəbi düzəldin, sonra yenidən cəhd etmək üçün tarixçəni təmizləyin." parked_other = "İşlədiləcək heç nə yoxdur: {{count}} sənəd əvvəl uğursuz olub və gözləməyə qoyulub. Səbəbi düzəldin, sonra yenidən cəhd etmək üçün tarixçəni təmizləyin." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Mənbələr" status = "Status" steps = "Addımlar" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Qovluq izləmə" @@ -8209,6 +8198,24 @@ label = "Jira issue-suna əlavə et" description = "Emal edilmiş sənədi əlavə kimi göndərir." label = "Sənədi email ilə göndər (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Emal edilmiş sənədi qovluğa yazır." label = "Nextcloud-a yüklə" @@ -8683,16 +8690,6 @@ maxed = "Limitə çatıb" subtitle = "Pulsuz sınağınız {{date}} tarixinə qədər davam edir. Kart tələb olunmur." title = "Enterprise sınağı" -[portal.search] -ariaLabel = "Axtar" -placeholder = "Stirling-də axtar — son nöqtələr, pipeline-lar, sənədlər…" - -[portal.search.empty] -noActionsDescription = "Sürətli əməliyyatlar əlçatan olduqda burada görünəcək." -noActionsTitle = "Sürətli əməliyyat yoxdur" -noMatches = "\"{{query}}\" üçün uyğunluq yoxdur" -noMatchesDescription = "Fərqli açar söz sınayın və ya kataloqa baxın." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Ləğv et" confirm = "Sil" title = "Mənbə silinsin?" -[portal.sources.empty] -description = "Qovluğu (və tezliklə cloud storage-i) qoşun ki, siyasətlərinizin sənədləri götürəcəyi yer olsun." -title = "Hələ heç bir mənbə qoşulmayıb" - [portal.sources.kpi] inUse = "İstifadədə" total = "Bağlantılar" @@ -8785,6 +8778,7 @@ unused = "İstifadə olunmur" documents = "Sənədlər" source = "Mənbə" status = "Status" +type = "Type" usedBy = "Siyasətlər" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Siyasətlər və Məxfilik" [settings.preferences] title = "Seçimlər" -[settings.search] -placeholder = "Ayar səhifələrində axtarın..." - [settings.security] description = "Hesabınızı təhlükəsiz saxlamaq üçün parolunuzu yeniləyin." title = "Təhlükəsizlik" @@ -10128,6 +10119,7 @@ last = "Son səhifə" maintainRatio = "Nisbətin qorunmasını dəyiş" next = "Növbəti səhifə" noSavedSigs = "Saxlanmış imza tapılmadı" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Şəxsi İmzalar" previous = "Əvvəlki səhifə" redo = "Yenidən et" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Arxa fonun silinməsi alınmadı" hint = "İmzanızın PNG və ya JPG şəklini yükləyin" label = "İmza şəklini yüklə" placeholder = "Şəkil faylı seç" +previewAlt = "Current image signature" processing = "Şəkil emal edilir..." removeBackground = "Ağ arxa fonu sil (şəffaf et)" @@ -10179,6 +10172,17 @@ saved = "Yuxarıdan saxlanmış imza seçin, sonra yerləşdirmək üçün PDF-i text = "Yuxarıda adınızı daxil etdikdən sonra, imzanızı yerləşdirmək üçün PDF üzərində istənilən yerə klikləyin." title = "İmza necə əlavə olunur" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "İmzanı hərəkət etdir" pause = "Yerləşdirməni dayandır" @@ -10625,6 +10629,23 @@ title = "Serverə yüklə" updateButton = "Serverdə yenilə" uploadButton = "Serverə yüklə" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Geri qayıt" pdfTools = "PDF alətləri" placeholder = "Başlamaq üçün alət seçin" premiumFeature = "Premium xüsusiyyət:" -searchTools = "Alətləri axtar" toolsHeader = "Alətlər" viewAllTools = "Bütün alətlərə bax" @@ -10822,6 +10842,9 @@ removal = "Silmə" signing = "İmzalama" verification = "Yoxlama" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Heç bir alət tapılmadı" noTools = "Heç bir alət mövcud deyil" @@ -10979,6 +11002,13 @@ approver = "Siyasəti təsdiqləyir" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "{{email}} ünvanına dəvət ləğv edilsin? Onlar cari linklə qoşula bilməyəcəklər." cancelInviteTitle = "Dəvəti ləğv et" @@ -10998,10 +11028,8 @@ title = "Hələ üzv yoxdur" addToTeam = "Komandaya əlavə et" guestCount = "{{count}} qonaq" guests = "Qonaqlar" -guestsDesc = "Xarici əməkdaşlar, paylaşdıqlarınızla məhdudlaşdırılıb. Yalnız Editor." ledBy = "{{owner}} tərəfindən idarə olunur" org = "Təşkilat" -orgDesc = "Təşkilat üzrə səlahiyyətə və siyasət təsdiqinə malik sahiblər" owners = "{{count}} sahib" team = "{{name}} komandası" teamMeta = "{{count}} nəfər" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "{{who}} tərəfindən dəvət olunub" cancel = "Ləğv et" -count = "{{count}} gözləmədə" -desc = "Hələ qoşulmamış dəvət olunan şəxslər. Qəbul edənədək yer tuturlar." expiresInDays_one = "Müddəti {{count}} gün sonra bitir" expiresInDays_other = "Müddəti {{count}} gün sonra bitir" expiresToday = "Müddəti bu gün bitir" title = "Gözləmədə olan dəvətlər" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Backend-ə qoşularkən xəta baş verdi və ya giriş icazəniz yoxdur. Yenidən cəhd edin." title = "Üzvlər yüklənə bilmədi" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Qoşmalarda axtar" title = "Qoşmalar" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Əlfəcin başlığı" bookmarkTitleRequired = "Əlfəcin başlığı tələb olunur" closeSidebar = "Əlfəcinlər yan panelini bağla" collapseAll = "Bütün əlfəcinləri yığ" +empty = "No bookmarks in this document" expandAll = "Bütün əlfəcinləri genişləndir" searchPlaceholder = "Əlfəcinləri axtar" +title = "Bookmarks" [viewer.comments] addComment = "Şərh əlavə et" @@ -11264,10 +11297,12 @@ locateAnnotation = "Sənəddə tap" moreActions = "Daha çox əməl" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Səhifə {{page}}" placingHint = "Yerləşdirmək üçün bir səhifəyə klikləyin… (ləğv et)" removeCommentOnly = "Yalnız şərhi sil" saveReply = "Cavabı saxla" +searchPlaceholder = "Search comments" title = "Şərhlər" typeComment = "Şərh" typeInsertText = "Mətn əlavə et" @@ -11290,6 +11325,7 @@ unsavedDesc = "Yadda saxlanılmamış dəyişiklikləriniz var" closeSidebar = "Qatlar yan panelini bağla" hideAll = "Bütün qatları gizlət" showAll = "Bütün qatları göstər" +title = "Layers" [viewer.link] delete = "Keçidi sil" @@ -11646,6 +11682,7 @@ exitRedaction = "Gizlətmə rejimindən çıx" exportAll = "PDF-i ixrac et" exportSelected = "Seçilmiş səhifələri ixrac et" formFill = "Formu doldur" +hideToolbar = "Hide toolbar" multiTool = "Çoxfunksiyalı alət" panMode = "Sürüşdürmə rejimi" print = "PDF-i çap et" @@ -11666,6 +11703,7 @@ selectAll = "Hamısını seç" selectByNumber = "Səhifə nömrələrinə görə seç" selectLanguage = "Dili seçin" share = "Paylaş" +showToolbar = "Show toolbar" toggleAnnotations = "Annotasiyaların görünməsini dəyiş" toggleAttachments = "Qoşmaları aç/bağla" toggleBookmarks = "Əlfəcinləri aç/bağla" diff --git a/frontend/editor/public/locales/bg-BG/translation.toml b/frontend/editor/public/locales/bg-BG/translation.toml index c744c2c8c0..512acbe237 100644 --- a/frontend/editor/public/locales/bg-BG/translation.toml +++ b/frontend/editor/public/locales/bg-BG/translation.toml @@ -1,7 +1,6 @@ alphabet = "Азбука" apply = "Приложи" applyAndContinue = "Запази и излез" -areYouSure = "Сигурни ли сте, че искате да напуснете?" back = "Назад" black = "Черно" blue = "Синьо" @@ -106,6 +105,7 @@ undoSuccess = "Операцията е отменена успешно" unknown = "Непознат" unpin = "Откачи файл (замяна след изпълнение на инструмента)" unsavedChanges = "Имате незаписани промени във вашия PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Незаписани промени" unsupported = "Неподдържано" username = "Потребителско име" @@ -1879,6 +1879,9 @@ width = "Ширина" [app] description = "Безплатната алтернатива на Adobe Acrobat (10M+ изтегляния)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Конвертиране в PDF/A-3b" convertToPdfA3bDescription = "Създава архивен PDF с вградени прикачени файлове" @@ -2933,12 +2936,16 @@ text = "За да направите тези разрешения непром [chat.actions] copy = "Копирайте съобщение" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Опции на агента Stirling" +assistant = "AI Assistant" clearChat = "Изчистете чата" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Тази операция ще използва вашите обла tooltip = "Изберете цвят от екрана" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Изберете цвят" [common] +actions = "Actions" back = "Назад" cancel = "Отказ" close = "Затвори" +codeSample = "Code sample" collapse = "Свий" confirm = "Потвърди" continue = "Продължи" copied = "Копирано!" copy = "Копирай" done = "Готово" +enterValue = "Enter value" error = "Грешка" expand = "Разгъни" loading = "Зареждане..." next = "Напред" +open = "Open" preview = "Преглед" previous = "Назад" refresh = "Опресни" remaining = "Оставащи" retry = "Повтори" save = "Запазване" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Изчисти избраните" @@ -3185,6 +3199,7 @@ title = "Метод на компресиране" [compress.settings] desiredSize = "Желан размер на файла" desiredSizePlaceholder = "Въведете размер" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Компресията е лесен начин да намалите размера на файла си. Изберете File Size, за да въведете целеви размер и ние ще нагласим качеството вместо вас. Изберете Quality, за да зададете силата на компресията ръчно." @@ -3387,6 +3402,24 @@ pdfOptions = "Опции за PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Първо изберете изходен формат" settings = "Настройки" single = "Единичен" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Търсене..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Редактиране" @@ -3824,11 +3861,13 @@ shareSelected = "Сподели избраните" sharing = "Споделяне" showAll = "Покажи всички" showHistory = "Покажи история" +sortBy = "Sort files" sortByDate = "Сортирай по дата" sortByName = "Сортирай по име" sortBySize = "Сортирай по размер" storage = "Хранилище" storageState = "Съхранение" +storageUsed = "Storage used" synced = "Синхронизирано" title = "Качване на PDF файлове" toolChain = "Приложени инструменти" @@ -3874,8 +3913,12 @@ addFiles = "Добавяне на файлове" addingFiles = "Добавяне на файлове…" collapse = "Свиване на страничната лента" customizeGroups = "Персонализиране на групи" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Отворете файлове, за да започнете" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Разгъване на страничната лента" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive не е конфигуриран" @@ -3888,15 +3931,17 @@ openFromComputer = "Отваряне от компютър" openSettings = "Отваряне на настройките" other = "Други" recent = "Скорошни" -search = "Търсене" -searchPlaceholder = "Търсене на файлове..." viewAll = "Виж всички {{count}} файла" [fileSidebar.fileItem] closeViewer = "Затворете визуализатора" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Отворете във визуализатора" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Възстанови по подразбиране" subtitle = "Групирайте файловете си в родителски категории. Добавете съществуващи или нови етикети към категория, преименувайте я или създайте своя. Файловете, които не са в нито една от вашите категории, се показват под „Други“." title = "Категории в страничната лента" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Добавете към работната зона" addToWorkspaceCount = "Добавете {{count}} към работната зона" @@ -3918,7 +3971,6 @@ backToMyFiles = "Назад към Моите файлове" breadcrumbs = "Път на папката" cancel = "Отказ" classification = "Класификация" -clearSearch = "Изчистете търсенето" clearSelection = "Изчистете избора" closeDetails = "Затворете подробностите" create = "Създайте" @@ -3950,6 +4002,7 @@ downloadAll = "Изтеглете всички" downloadVersion = "Изтеглете тази версия" dropOverlay = "Пуснете файлове за качване" dropOverlaySub = "Файловете започват като Local. Използвайте 'Move to' или 'Save to cloud', за да ги организирате в папка." +duplicate = "Duplicate" file = "Файл" fileInfo = "File info" fileMenu = "Действия за файл" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Преоразмерете дървото на папкит save = "Запазете" saveToServer = "Запазете на сървъра" saveToServerDisabledHint = "Запазването към сървъра не е активирано на този сървър. Помолете администратора да го активира." -search = "Търсене" -searchPlaceholder = "Търсене в тази папка и подпапките" selectAll = "Изберете всички" selectAllHint = "Кликнете, за да изберете всички. Съвет: задръжте Ctrl (или Cmd), за да добавяте файлове поотделно, Shift за избор на диапазон." selectedCount = "{{count}} избрани" @@ -4044,6 +4095,10 @@ title = "Още няма облачни файлове" hint = "Файловете, записани без качване, остават тук. Пуснете файл, за да добавите такъв." title = "Няма само локални файлове" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Файловете, които отваряте или редактирате, ще се показват тук." title = "Все още няма промени" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Папката не можа да бъде изтрита." deleteFolderFailedDetail = "Папката не можа да бъде изтрита: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Неуспешно обновяване на външния вид на папката." folderAppearanceFailedDetail = "Неуспешно обновяване на външния вид на папката: {{message}}" moveFilesFailed = "Файловете не можаха да бъдат преместени." @@ -4113,6 +4170,11 @@ localHint = "Съхранява се само в този браузър" shared = "Споделени" sharedHint = "Споделени с вас чрез връзка" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Най-старите първо" modifiedDesc = "Най-новите първо" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Няма налични файлове в хранили open = "Отвори" openFile = "Отвори файл" openFiles = "Отвори файлове" +selectFile = "Select {{name}}" selectFromStorage = "Изберете от хранилище" upload = "Качване" uploadFile = "Качване на файл" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Всичко е запазено" +analyzingFields = "Analysing form fields..." extractCsvError = "Неуспешно извличане на CSV" extractXlsxError = "Неуспешно извличане на XLSX" +filled = "filled" flattenAfterFilling = "Фиксиране след попълване" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "зад." requiredFieldsError = "Моля, попълнете всички задължителни полета" rescanFields = "Пресканирайте полетата" @@ -4795,9 +4863,6 @@ title = "Изображение към PDF" [imageToPdf] tags = "преобразуване,img,jpg,изображение,снимка" -[infoBanner] -dismiss = "Отхвърлете" - [invite] acceptError = "Неуспешно създаване на акаунт" accountFor = "Създаване на акаунт за" @@ -4979,6 +5044,34 @@ uploadSuccess = "Качването е успешно!" uploadSuccessMessage = "Вашите изображения бяха прехвърлени." validating = "Валидиране на сесията..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Сканирайте, за да качите снимки. Изображенията се конвертират автоматично в PDF." descriptionNoConvert = "Сканирайте, за да качвате снимки от мобилното си устройство." @@ -4997,8 +5090,13 @@ title = "Качване от мобилно устройство" tags = "Мултиинструмент,Мулти операции,UI,плъзгане с щракване,потребителска част,страна на клиента,интерактивен,неразрешим,преместване" title = "PDF Мулти инструмент" -[navbar] -search = "Търсене" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Удостоверяването беше неуспешно. Можете да затворите този прозорец и да опитате отново." @@ -5127,6 +5225,7 @@ activeFiles = "Изгледът Активни файлове allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Затвори" cropSettings = "След като избрахме файла за изрязване, можем да конфигурираме инструмента Изрязване, за да изберем областта, до която да изрежем PDF." +dialogLabel = "Onboarding" fileCheckbox = "Кликването върху файл го избира за обработка. Можете да избирате множество файлове за пакетни операции." fileReplacement = "Промененият файл автоматично ще замени оригинала в Работната зона, което позволява лесно да го пуснете през още инструменти." filesButton = "Бутонът Файлове в лентата за бърз достъп ви позволява да качите PDF файлове, върху които да използвате инструментите." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF към PDF/A" tags = "архив,дълготраен,стандартен,преобразуване,съхранение,консервиране" title = "PDF към PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "печат,стандарт,конвертиране,производство,предпечат,архив" title = "PDF към PDF/X" @@ -6045,6 +6151,7 @@ small = "500 кредита" xsmall = "100 кредита" [plan.availablePlans] +currency = "Billing currency" subtitle = "Изберете план, който отговаря на нуждите ви" title = "Налични планове" @@ -6624,6 +6731,7 @@ reachedTitle = "Месечният лимит на разходите е дос title = "Stripe порталът не можа да бъде отворен" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "от {{allowance}} безплатни PDF използвани" capSuffix_other = "от {{allowance}} безплатни PDF използвани" eyebrow = "Пробен Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Изпратете документ или известие по email, когато правило се изпълнява." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Записвайте обработени документи в Nextcloud." @@ -7077,11 +7211,6 @@ title = "Официални SDKs" beta = "Beta" deprecated = "Deprecated" -[portal.docs.search] -empty = "Няма съвпадащи документи" -placeholder = "Търсене в документи" -results = "{{count}} резултата" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Включени, именувани способности, които вашият агент извиква като един инструмент. Всяко skill е детерминирана верига от операции с прикачени evals." @@ -7180,11 +7309,11 @@ editorAction = "Редактор" empty = "Няма документи, отговарящи на този филтър." rowActions = "Действия за реда" sensitiveLabel = "Чувствителен" -sensitiveTitle = "Чувствителен — необходим е достъп" [portal.documents.table.columns] action = "Pipeline / Действие" document = "Документ" +labels = "Labels" product = "Продукт" status = "Статус" time = "Време" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Последно видян" region = "Регион" status = "Статус" +target = "Target" version = "Версия" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Този изглед срещна неочаквана греш retry = "Опитайте отново" title = "Нещо се обърка на тази страница" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Назад" body = "Изтеглете desktop app или self-host-нете server-а." @@ -7380,11 +7560,6 @@ cancel = "Отказ" confirm = "Отмяна на ключ" title = "Отмяна на API ключ" -[portal.infrastructure.attestationLabel] -attested = "Удостоверено" -inScope = "В обхвата" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Филтриране на одитни събития по категория" heading = "Одитни logs" @@ -7456,11 +7631,6 @@ info = "Информация" success = "Успех" warning = "Предупреждение" -[portal.infrastructure.certLabel] -certified = "Сертифицирано" -inProgress = "В процес" -notStarted = "Не е започнато" - [portal.infrastructure.createKey] cancel = "Отказ" createKey = "Създай ключ" @@ -7474,238 +7644,10 @@ subtitleCreated = "Копирайте тази тайна сега — няма title = "Създай API ключ" titleCreated = "Ключът е създаден" -[portal.infrastructure.deployLabel] -live = "На живо" -queued = "На опашка" -rolledBack = "Върнато назад" -rolling = "Разгръща се" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Разгърнато от" -environment = "Среда" -product = "Продукт" -status = "Статус" -version = "Версия" -when = "Кога" - -[portal.infrastructure.deployments.recent] -heading = "Скорошни разгръщания" -subheading = "Най-новите внедрявания в продукти и среди." - -[portal.infrastructure.deployments.regionColumns] -instances = "Инстанции" -latency = "Латентност" -load = "Натоварване" -p99 = "P99" -region = "Регион" -status = "Статус" -throughput = "Пропускателност" -uptime = "Uptime" -version = "Версия" - -[portal.infrastructure.deployments.regions] -heading = "Региони" -subheading = "Здраве на живо за всеки разгърнат Stirling регион — латентност, натоварване и версия на внедряването." - -[portal.infrastructure.deployments.regions.empty] -description = "Разгърнатите региони ще се появят тук, след като работното ви пространство бъде provisioned." -title = "Няма разгърнати региони" - [portal.infrastructure.keyLabel] active = "Активен" revoked = "Отменен" -[portal.infrastructure.modelLabel] -active = "Активен" -degraded = "Влошен" -disabled = "Деактивиран" - -[portal.infrastructure.models] -heading = "Модели" -msValue = "{{value}} ms" -subheading = "Каталогът с модели и routing, които задвижват обработката на документи във вашето работно пространство." - -[portal.infrastructure.models.byom] -description = "Регистрирайте on-prem или самостоятелно хостван модел и го фиксирайте към регион за обработка, обвързана с местонахождението на данните." -title = "Bring your own model" - -[portal.infrastructure.models.catalogue] -heading = "Каталог" -sub = "Управлявани модели, достъпни за вашето работно пространство, с латентност и цена на живо." -subEnterprise = "Управлявани, bring-your-own и on-prem модели — с налично фиксиране по регион." - -[portal.infrastructure.models.catalogue.empty] -description = "Моделите в каталога на вашето работно пространство ще се появят тук." -title = "Няма налични модели" - -[portal.infrastructure.models.columns] -cost = "Цена" -latency = "Латентност" -load = "Натоварване" -model = "Модел" -status = "Статус" -type = "Тип" -version = "Версия" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/извикване" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Активни модели" -avgLatency = "Средна латентност" -included = "Включено" -monthlySpend = "Месечни разходи за модели" - -[portal.infrastructure.models.routing] -empty = "Няма конфигурирани правила за routing." -heading = "Правила за routing" -sub = "Кой модел обработва всяка операция. Стойността по подразбиране се прилага, когато няма по-тясно правило." -subLocked = "Насочвайте операции към конкретни модели — достъпно в платени планове." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Надстройте до Pro, за да контролирате кой модел обработва всяка операция и тип документ." -title = "Model routing е платена функция" - -[portal.infrastructure.models.routingColumns] -default = "По подразбиране" -docType = "Тип документ" -modelForAria = "Модел за {{operation}}" -operation = "Операция" -routedTo = "Насочено към" - -[portal.infrastructure.modelTypeLabel] -classification = "Класификация" -extraction = "Извличане" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Влошен" -down = "Недостъпен" -healthy = "Здрав" - -[portal.infrastructure.security.access.byok] -description = "Предоставете ключ от вашия собствен KMS. Stirling криптира с него, но все още може да чете." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Ключовете никога не напускат вашия KMS. Stirling държи само ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling управлява ключовете за криптиране. Най-просто — нулеви операции с ключове от ваша страна." -label = "Ключове, държани от Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Политика за достъп до документи" -subheading = "Контролира кой може да декриптира обработени документи в покой." - -[portal.infrastructure.security.attestations] -heading = "Атестации за съответствие" -noReport = "Няма наличен отчет" -subheading = "Одитно състояние по рамки, с налични отчети за атестирани контроли." -viewReport = "Вижте отчета →" - -[portal.infrastructure.security.compliance] -heading = "Съответствие" -subheading = "Атестации и сертификации, покриващи платформата Stirling." - -[portal.infrastructure.security.empty] -description = "Конфигурацията за сигурност на вашето работно пространство ще се появи тук." -title = "Състоянието на сигурността не е налично" - -[portal.infrastructure.security.hyokBanner] -description = "С HYOK ключовете за криптиране никога не напускат вашия KMS. Stirling съхранява и обработва само ciphertext, който можете да оттеглите по всяко време." -title = "Stirling не може да декриптира вашите документи" - -[portal.infrastructure.security.ipAllowlist] -empty = "Няма конфигурирани IP диапазони — всички IP адреси са разрешени." -heading = "IP allowlist" -sub = "API достъпът е ограничен до тези CIDR диапазони." -subLocked = "Ограничете API достъпа до известни IP диапазони — достъпно в платени планове." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Надстройте до Pro, за да ограничите API достъпа до конкретни мрежи." -title = "IP allowlisting е платена функция" - -[portal.infrastructure.security.ipColumns] -added = "Добавено" -addedBy = "Добавено от" -cidr = "CIDR" -label = "Етикет" - -[portal.infrastructure.security.keyManagement] -algorithm = "Алгоритъм" -heading = "Управление на ключове за криптиране" -keyId = "Идентификатор на ключ" -lastRotated = "Последно ротиран" -rotateKey = "Ротирай ключ" -rotationPolicy = "Политика за ротация" -subheading = "Попечителство на ключовете, които криптират документи в покой — кой може да декриптира и как се ротират ключовете." - -[portal.infrastructure.security.managedBanner] -description = "Попечителството bring-your-own-key (BYOK) и hold-your-own-key (HYOK) е достъпно в Enterprise. Надстройте, за да предоставяте ключове от вашия собствен KMS." -title = "Ключовете се управляват от Stirling във вашия план" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Азиатско-тихоокеански регион" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR граница на данните" -label = "Европейски съюз" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Съединени щати" - -[portal.infrastructure.security.residencyHeader] -heading = "Местонахождение на данните" -subheading = "Къде се съхраняват и обработват документите." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} използвани" - -[portal.infrastructure.storage.empty] -description = "Свързаното storage и използване ще се появят тук." -title = "Няма конфигурирано storage" - -[portal.infrastructure.storage.lifecycle] -active = "Активно" -activeRange = "0–{{value}}d" -archived = "Архивирано" -coldStorage = "cold storage" -deleted = "Изтрито" -never = "никога" -purged = "изчистено" - -[portal.infrastructure.storage.providers] -connect = "Свържи" -connected = "Свързано" -heading = "Свързани доставчици" -subheading = "Къде се записват обработените артефакти." - -[portal.infrastructure.storage.retention] -heading = "Задържане" -subheading = "Колко дълго се пазят артефактите преди изтриване по lifecycle." -windowLabel = "Прозорец за задържане по подразбиране" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} ден" -days_other = "{{count}} дни" -never = "Никога не изтривай" - -[portal.infrastructure.storage.totalUsage] -heading = "Общо използване" -progressLabel = "Използвано storage" -subheading = "Storage, използвано във всички свързани доставчици." - [portal.infrastructure.tabs] apiKeys = "API ключове" audit = "Одитни logs" @@ -7715,11 +7657,9 @@ security = "Сигурност" storage = "Storage" [portal.integrations] -addAnother = "Добавяне на още една" availableHeading = "Налични" comingSoonHeading = "Очаквайте скоро" connect = "Свързване" -connectedHeading = "Свързани" connectionCount_one = "{{count}} връзка" connectionCount_other = "{{count}} връзки" customApi = "Персонализиран API" @@ -7737,6 +7677,10 @@ security = "Сигурност" signing = "Подписване" storage = "Съхранение" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Свързано" @@ -7767,7 +7711,6 @@ integrations = "Интеграции" pipelines = "Pipelines" policies = "Политики" procurement = "Снабдяване" -settings = "Настройки" sources = "Източници" usage = "Използване и фактуриране" users = "Потребители" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Нов pipeline" [portal.pipelines.builder] -addStep = "Добави инструмент" +activate = "Activate" back = "Назад към pipelines" cannotFollow = "Не може да приеме {{produced}}" chooseAccount = "Изберете акаунт" @@ -7792,28 +7735,38 @@ chooseDestination = "Изберете дестинация" chooseOperation = "Изберете какво прави тази стъпка" chooseSource = "Изберете източник" discard = "Отхвърли промените" -enabled = "Активирано" inputs = "Вход" inputSource = "Входен източник" inputTrigger = "Тригер" keepEditing = "Продължи редактирането" +moreActions = "More actions" needsConfiguring = "Нуждае се от настройка" -needsUpload = "Нуждае се от качен файл" -noSources = "Все още няма свързани източници. Създайте такъв, за да го използвате като вход." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Няма инструменти, отговарящи на търсенето ви." -pipelineSettings = "Настройки на pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Търсене на инструменти" -selectToolBody = "Добавете инструмент, за да изградите вашия pipeline." -selectToolTitle = "Все още няма инструменти" sendToSystem = "Изпращане към друга система" stepsIncompatible = "Тези стъпки не могат да се изпълнят върху това, което произвежда предишната им стъпка: {{tools}}." stepsNeedSetup = "Тези стъпки все още трябва да бъдат настроени преди запазване: {{tools}}." -toolSettings = "Настройки на инструмента" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Неразпозната операция, запазена както е." unsavedBody = "Имате незапазени промени. Запазете ги преди да напуснете или ги отхвърлете?" unsavedTitle = "Незапазени промени" -uploadUnsupported = "Качените файлове все още не се поддържат в pipelines, затова тези стъпки не могат да бъдат запазени: {{tools}}." usesDefaults = "Изпълнява се с настройки по подразбиране" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Комбинира всеки файл от предишната стъпка" @@ -7825,31 +7778,36 @@ undeclared-operation = "Не може да се провери какво при [portal.pipelines.composer] addTool = "Добави инструмент" -cancel = "Отказ" -chainEmpty = "Добавете инструмент, за да започнете да изграждате вашия pipeline." create = "Създай pipeline" +createPaused = "Create paused" editingUnsupported = "Показването на тези параметри на инструмента за редактиране все още не се поддържа." -moveDown = "Премести надолу" -moveUp = "Премести нагоре" +editSource = "Edit source" name = "Име" namePlaceholder = "напр. Redaction sweep" noToolSettings = "Този инструмент няма конфигурируеми настройки." -operations_one = "Операция ({{count}})" -operations_other = "Операции ({{count}})" output = "Изход" -removeStep = "Премахни операция" save = "Запази промените" scheduleEvery = "Изпълнявай на всеки" -sources = "Източници" -sourcesLoading = "Зареждане на източници..." trigger = "Trigger" triggerManual = "Само ръчно" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "дни" hours = "часа" minutes = "минути" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Да се изтрие ли \"{{name}}\"? Това не може да бъде отменено." cancel = "Отказ" @@ -7867,6 +7825,37 @@ connectSource = "Свързване на източник" description = "Създайте първия си pipeline: изберете източниците, върху които се изпълнява, свържете операциите и изберете къде да отива изходът." title = "Все още няма pipelines" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Активни" paused = "Поставени на пауза" @@ -7879,7 +7868,6 @@ completed_one = "Изпълнението завърши." completed_other = "Всички {{count}} изпълнения завършиха." empty = "Няма какво да се изпълни: източниците нямаха документи за обработка." failed = "Изпълнението не бе успешно: {{error}}" -historyCleared = "Историята е изчистена. Следващото изпълнение ще обработи отново всичко, което в момента е в източниците." inFlight = "Няма нищо ново за изпълнение: документи все още се обработват от предишно изпълнение." parked_one = "Няма нищо за изпълнение: {{count}} документ е бил неуспешен преди и е паркиран. Отстранете причината, след това изчистете историята, за да опитате отново." parked_other = "Няма нищо за изпълнение: {{count}} документа са били неуспешни преди и са паркирани. Отстранете причината, след това изчистете историята, за да опитате отново." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Източници" status = "Статус" steps = "Стъпки" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Наблюдение на папка" @@ -8209,6 +8198,24 @@ label = "Прикачване към Jira issue" description = "Изпраща обработения документ като прикачен файл." label = "Изпращане на документа по имейл (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Записва обработения документ в папка." label = "Качване в Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Достигнат максимум" subtitle = "Вашият безплатен trial продължава до {{date}}. Не се изисква карта." title = "Enterprise trial" -[portal.search] -ariaLabel = "Търсене" -placeholder = "Търсене в Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Бързите действия ще се появят тук, когато са налични." -noActionsTitle = "Няма бързи действия" -noMatches = "Няма съвпадения за „{{query}}“" -noMatchesDescription = "Опитайте с друга ключова дума или разгледайте каталога." - [portal.settings.groups] admin = "Администратор" @@ -8743,10 +8740,6 @@ cancel = "Отказ" confirm = "Изтриване" title = "Да се изтрие ли източникът?" -[portal.sources.empty] -description = "Свържете папка (а скоро и cloud storage), така че вашите политики да имат откъде да вземат документи." -title = "Все още няма свързани източници" - [portal.sources.kpi] inUse = "В употреба" total = "Връзки" @@ -8785,6 +8778,7 @@ unused = "Неизползван" documents = "Документи" source = "Източник" status = "Статус" +type = "Type" usedBy = "Политики" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Политики и поверителност" [settings.preferences] title = "Предпочитания" -[settings.search] -placeholder = "Търсене в страниците с настройки..." - [settings.security] description = "Актуализирайте паролата си, за да запазите акаунта си защитен." title = "Сигурност" @@ -10128,6 +10119,7 @@ last = "Последна страница" maintainRatio = "Превключване за поддържане на съотношението на страните" next = "Следваща страница" noSavedSigs = "Не са намерени запазени подписи" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Лични подписи" previous = "Предишна стараница" redo = "Повтори" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Неуспешно премахване на ф hint = "Качете PNG или JPG изображение на вашия подпис" label = "Качете изображение на подпис" placeholder = "Изберете файл с изображение" +previewAlt = "Current image signature" processing = "Обработка на изображението..." removeBackground = "Премахване на бял фон (прозрачен)" @@ -10179,6 +10172,17 @@ saved = "Изберете запазен подпис по-горе, след т text = "След като въведете името си по-горе, щракнете навсякъде върху PDF-а, за да поставите подписа." title = "Как да добавите подпис" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Преместване на подпис" pause = "Пауза на поставянето" @@ -10625,6 +10629,23 @@ title = "Качване на сървъра" updateButton = "Актуализиране на сървъра" uploadButton = "Качване на сървъра" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Върнете се" pdfTools = "PDF инструменти" placeholder = "Изберете инструмент, за да започнете" premiumFeature = "Премиум функция:" -searchTools = "Търсене на инструменти" toolsHeader = "Инструменти" viewAllTools = "Виж всички инструменти" @@ -10822,6 +10842,9 @@ removal = "Премахване" signing = "Подписване" verification = "Проверка" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Няма намерени инструменти" noTools = "Няма налични инструменти" @@ -10979,6 +11002,13 @@ approver = "Одобрява политика" editor = "Редактор" processor = "Процесор" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Да се отмени ли поканата до {{email}}? Те няма да могат да се присъединят с текущата връзка." cancelInviteTitle = "Отмяна на поканата" @@ -10998,10 +11028,8 @@ title = "Все още няма членове" addToTeam = "Добавяне към екип" guestCount = "{{count}} гост" guests = "Гости" -guestsDesc = "Външни сътрудници, ограничени до това, което сте споделили. Само редактор." ledBy = "воден от {{owner}}" org = "Организация" -orgDesc = "Собственици с правомощия в цялата организация и одобрение на политики" owners = "{{count}} собственик" team = "Екип {{name}}" teamMeta = "{{count}} души" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Поканен от {{who}}" cancel = "Отказ" -count = "{{count}} чакащи" -desc = "Поканени хора, които още не са се присъединили. Те заемат място, докато не приемат." expiresInDays_one = "Изтича след {{count}} ден" expiresInDays_other = "Изтича след {{count}} дни" expiresToday = "Изтича днес" title = "Чакащи покани" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Нещо се обърка при свързването със сървърната част или нямате достъп. Опитайте отново." title = "Членовете не можаха да бъдат заредени" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Търсене в прикачените файлове" title = "Прикачени файлове" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Заглавие на отметката" bookmarkTitleRequired = "Заглавието на отметката е задължително" closeSidebar = "Затвори страничната лента с отметки" collapseAll = "Свий всички отметки" +empty = "No bookmarks in this document" expandAll = "Разгъни всички отметки" searchPlaceholder = "Търсене в отметките" +title = "Bookmarks" [viewer.comments] addComment = "Добавяне на коментар" @@ -11264,10 +11297,12 @@ locateAnnotation = "Намиране в документа" moreActions = "Още действия" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Страница {{page}}" placingHint = "Щракнете върху страница, за да поставите… (отказ)" removeCommentOnly = "Премахване само на коментара" saveReply = "Запис на отговора" +searchPlaceholder = "Search comments" title = "Коментари" typeComment = "Коментар" typeInsertText = "Вмъкване на текст" @@ -11290,6 +11325,7 @@ unsavedDesc = "Имате незаписани промени" closeSidebar = "Затвори страничната лента със слоеве" hideAll = "Скрий всички слоеве" showAll = "Покажи всички слоеве" +title = "Layers" [viewer.link] delete = "Изтриване на връзка" @@ -11646,6 +11682,7 @@ exitRedaction = "Изход от режим на заличаване" exportAll = "Експортиране на PDF" exportSelected = "Експортиране на избраните страници" formFill = "Попълване на формуляр" +hideToolbar = "Hide toolbar" multiTool = "Мултиинструмент" panMode = "Режим на преместване" print = "Печат на PDF" @@ -11666,6 +11703,7 @@ selectAll = "Избиране на всички" selectByNumber = "Избор по номера на страници" selectLanguage = "Избор на език" share = "Сподели" +showToolbar = "Show toolbar" toggleAnnotations = "Превключване на видимостта на анотациите" toggleAttachments = "Превключване на прикачените файлове" toggleBookmarks = "Превключване на отметките" diff --git a/frontend/editor/public/locales/bo-CN/translation.toml b/frontend/editor/public/locales/bo-CN/translation.toml index 92b86f483a..7d0c65d14b 100644 --- a/frontend/editor/public/locales/bo-CN/translation.toml +++ b/frontend/editor/public/locales/bo-CN/translation.toml @@ -1,7 +1,6 @@ alphabet = "ཡི་གེ།" apply = "འཇུག" applyAndContinue = "སྲུང་དང་ཕྱིར་འབུད།" -areYouSure = "ཁྱེད་རང་འགྲོ་འདོད་ཡོད་དམ།" back = "ཕྱིར་ལོག" black = "ནག་པོ" blue = "སྔོན་པོ" @@ -106,6 +105,7 @@ undoSuccess = "ལས་དོན་ལེགས་འགྲུབ་བྱུ unknown = "མི་ཤེས" unpin = "ཨན་པིན་ཡིག་ཆ། (ལག་ཆའི་བཀོལ་སྤྱོད་རྗེས་ཚབ་བྱེད་པ)" unsavedChanges = "ཁྱེད་རང་ལ་ཁྱེད་རང་གི་PDFལ་འགྱུར་བ་མེད་པའི་འགྱུར་བ་ཡོད།" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "གསོག་འཇོག་མེད་པའི་འགྱུར་བ།" unsupported = "རྒྱབ་སྐྱོར་མེད་པ།" username = "མིང་།" @@ -1879,6 +1879,9 @@ width = "ཞེང་ཚད" [app] description = "ཕི་རི་ཨེ་ཌོབ་ཨེ་ཀྲོ་བྷཊ་ཐབས་གཞན་ཞིག (10M+ ཕབ་ལེན།)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "PDF/A-3b ལ་སྒྱུར་བ།" convertToPdfA3bDescription = "ཡིག་ཚགས་ཀྱི་PDFཞིག་བཙུགས་ནས་བཙུགས་པའི་མཐུད་ཁ་བཟོས།" @@ -2933,12 +2936,16 @@ text = "ཆོག་མཆན་འདི་དག་འགྱུར་བ་མ [chat.actions] copy = "འཕྲིན་དོན་འདྲ་བཤུས" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling agent འདེམས་ཚན" +assistant = "AI Assistant" clearChat = "གླེང་མོལ་བཤོལ" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "བཀོལ་སྤྱོད་འདིས་ཁྱེད་ཀྱ tooltip = "བརྙན་ཡོལ་ནས་ཁ་དོག་འདེམས" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "ཁ་དོག་འདེམས།" [common] +actions = "Actions" back = "རྒྱབ་ངོས" cancel = "འདོར་བ" close = "གཏན་པ" +codeSample = "Code sample" collapse = "རྡིབ་པ" confirm = "གཏན་འཁེལ" continue = "མུ་མཐུད" copied = "འདྲ་བཤུས་བྱས།" copy = "འདྲ་བཤུས།" done = "བགྱིས་པ" +enterValue = "Enter value" error = "ནོར་འཁྲུལ" expand = "རྒྱ་སྐྱེད་གཏོང" loading = "ཁུར་པོ།" next = "རྗེས་མ" +open = "Open" preview = "སྔོན་ལྟ།" previous = "སྔོན་ཆད་ཀྱི" refresh = "གསར་སྐྲུན་བྱེད་པ།" remaining = "ལྷག་མ" retry = "ངལ་གསོ་ཡང་བསྐྱར་བྱེད་པ།" save = "སྐྱོབ་པ" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "བདམས་ཐོན་བྱུང་བར་གསལ་བ།" @@ -3185,6 +3199,7 @@ title = "བསྡམས་སྒྲིལ་ཐབས་ལམ།" [compress.settings] desiredSize = "དགོས་མཁོའི་ཡིག་ཆའི་ཆེ་ཆུང" desiredSizePlaceholder = "ཆེ་ཆུང་བཙུགས" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "ཁྱེད་ཀྱི་ཡིག་ཆའི་ཆེ་ཆུང་ཉུང་དུ་གཏོང་བའི་ཐབས་ཤེས་སྟབས་བདེ་ཞིག་ཡིན། དམིགས་འབེན་གྱི་ཚད་གཞི་ནང་འཇུག་བྱེད་པར་ཡིག་ཆའི་ཚད་གཞི་འདེམས་ནས་ང་ཚོ་ཁྱེད་ཀྱི་ཆེད་དུ་སྤུས་ཚད་སྙོམ་སྒྲིག་བྱེད་དགོས། ལག་ཐོག་ནས་བསྡམས་སྒྲིལ་གྱི་ཤུགས་ཚད་སྒྲིག་པའི་སྤུས་ཚད་འདེམས།" @@ -3387,6 +3402,24 @@ pdfOptions = "PDF གདམ་ག།" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "ཐོན་ཁུངས་རྣམ་གཞག་ཐོག་མར་འདེམས།" settings = "སྒྲིག་སྟངས།" single = "ཁེར་ཧྲེང" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "འཚོལ..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "ཞུན་དག" @@ -3824,11 +3861,13 @@ shareSelected = "འདེམས་པ་མཉམ་སྤྱོད" sharing = "མཉམ་སྤྱོད" showAll = "ཚང་མ་སྟོན་དང་།" showHistory = "ལོ་རྒྱུས།" +sortBy = "Sort files" sortByDate = "ཚེས་གྲངས་ཀྱིས་དབྱེ་བ།" sortByName = "མིང་གིས་དབྱེ་བ།" sortBySize = "ཚད་གཞི་ལྟར་དབྱེ་བ།" storage = "ཤོང་ཚད" storageState = "གསོག་འཇོག" +storageUsed = "Storage used" synced = "མཉམ་བསྒྲིག" title = "Upload PDF ཡིག་ཆ།" toolChain = "ལག་ཆས་བཀོལ་སྤྱོད་བྱས།" @@ -3874,8 +3913,12 @@ addFiles = "ཡིག་ཆ་ཁ་སྣོན" addingFiles = "ཡིག་ཆ་སྣོན་བཞིན་པ…" collapse = "ཟུར་པང་བསྡམས" customizeGroups = "ཚོགས་པ་རང་སྒྲིག" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "འགོ་རྩོམ་བྱེད་པར་ཡིག་ཆ་ཁ་ཕྱེ" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "ཟུར་པང་རྒྱས" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive སྒྲིག་འགོད་མ་བྱས" @@ -3888,15 +3931,17 @@ openFromComputer = "གློག་ཀླད་ནས་ཁ་ཕྱེ" openSettings = "སྒྲིག་སྟངས་ཁ་ཕྱེ" other = "གཞན" recent = "ཉེ་ཆར" -search = "འཚོལ" -searchPlaceholder = "ཡིག་ཆ་འཚོལ..." viewAll = "{{count}} ཡིག་ཆ་ཚང་མ་ལྟ" [fileSidebar.fileItem] closeViewer = "མཐོང་ཆས་ཁ་རྒྱག" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "མཐོང་ཆས་ནང་ཁ་ཕྱེ" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "ཚང་མ་སྟོན" subtitle = "ཡིག་ཆའི་ཟུར་སྟོན་ནང་གི་རིགས་དབྱེ་སྟོན་པའམ་སྦས་པ།" title = "ཟུར་སྟོན་རིགས་དབྱེ" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "ལས་གནས་ལ་ཁ་སྣོན" addToWorkspaceCount = "{{count}} ལས་གནས་ལ་ཁ་སྣོན" @@ -3918,7 +3971,6 @@ backToMyFiles = "ངའི་ཡིག་ཆར་ཕྱིར་ལོག" breadcrumbs = "སྣོད་ཁུངས་ལམ" cancel = "འདོར" classification = "རིགས་དབྱེ" -clearSearch = "འཚོལ་བཤོལ" clearSelection = "འདེམས་བཤོལ" closeDetails = "ཞིབ་ཕྲ་ཁ་རྒྱག" create = "གསར་བཟོ" @@ -3950,6 +4002,7 @@ downloadAll = "ཚང་མ་ཕབ་ལེན" downloadVersion = "པར་གཞི་འདི་ཕབ་ལེན" dropOverlay = "ཡིག་ཆ་བཀོག་ནས་ཡར་འཇུག" dropOverlaySub = "ཡིག་ཆ་ཚང་མ Local ནས་འགོ་བཙུགས། 'Move to' ཡང་ན 'Save to cloud' བེད་སྤྱོད་ནས་སྣོད་འགེངས་ནང་སྒྲིག་འགོད་བྱེད།" +duplicate = "Duplicate" file = "ཡིག་ཆ" fileInfo = "File info" fileMenu = "ཡིག་ཆའི་བྱ་བ" @@ -3983,8 +4036,6 @@ resizeFolderTree = "སྣོད་འགེངས་རྩ་འབྲེལ་ save = "སྲུངས" saveToServer = "སར་བར་ཉར" saveToServerDisabledHint = "སར་བར་ལ་ཉར་བ་འདི་སར་བར་འདིར་སྤྱོད་མེད། དོ་དམ་པ་ལ་དབང་སྤྲོད་རོགས།" -search = "འཚོལ" -searchPlaceholder = "སྣོད་འགེངས་འདི་དང་ཡན་ལག་སྣོད་འགེངས་ནང་འཚོལ" selectAll = "སྤྱིར་འདེམས" selectAllHint = "མཐེབ་གནོན་ནས་ཚང་མ་འདེམས། ཕན་བསླབ: Ctrl (ཡང་ན Cmd) བཟུང་ནས་ཡིག་ཆ་རྡོག་རྡོག་སྣོན ཡང་ན Shift བཟུང་ནས་ཁྱབ་ཁོངས་འདེམས།" selectedCount = "{{count}} འདེམས་ཟིན" @@ -4044,6 +4095,10 @@ title = "cloud ཡིག་ཆ་ད་དུང་མེད" hint = "ཡར་འཇུག་མ་བྱས་པའི་ཡིག་ཆ་ཚང་མ་འདིར་སྲུངས། ཡིག་ཆ་བཀོག་ནས་ཁ་སྣོན།" title = "Local ཙམ་གྱི་ཡིག་ཆ་མེད" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "ཁྱེད་ཀྱིས་ཁ་ཕྱེ་བ་དང་ཞུན་དག་བྱས་པ་ཚང་མ་འདིར་མངོན།" title = "ད་དུང་བཟོ་བཅོས་མི་བྱས" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "སྣོད་འགེངས་སུབ་མ་ཐུབ།" deleteFolderFailedDetail = "སྣོད་འགེངས་སུབ་མ་ཐུབ: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "སྣོད་འགེངས་ཚོར་སྣང་བསྒྱུར་མ་ཐུབ།" folderAppearanceFailedDetail = "སྣོད་འགེངས་ཚོར་སྣང་བསྒྱུར་མ་ཐུབ: {{message}}" moveFilesFailed = "ཡིག་ཆ་སྤོ་མ་ཐུབ།" @@ -4113,6 +4170,11 @@ localHint = "མི་སྣང་ཆས་འདི་ནང་ཙམ་ཉར" shared = "མཉམ་སྤྱོད" sharedHint = "འབྲེལ་མཐུད་བརྒྱུད་ནས་མཉམ་སྤྱོད" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "དུས་རྙིང་བ་སོང་མ" modifiedDesc = "ཉེ་ཆར་སྔོན" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "གསོག་འཇོག་ནང་ཡིག་ཆ open = "འབྱེད" openFile = "ཁ་ཕྱེ་བའི་ཡིག་ཆ།" openFiles = "ཁ་ཕྱེ་བའི་ཡིག་ཆ།" +selectFile = "Select {{name}}" selectFromStorage = "གསོག་འཇོག་ཁང་ནས་འདེམས།" upload = "Upན།" uploadFile = "Upload ཡིག་ཆ།" @@ -4241,9 +4304,14 @@ issues = "གྷི་ཊི་ཧབ།" [formFill] allSaved = "ཚང་མ་ཉར་ཚགས་བྱས་ཟིན" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV ཕྱིར་འདོན་མ་ཐུབ" extractXlsxError = "XLSX ཕྱིར་འདོན་མ་ཐུབ" +filled = "filled" flattenAfterFilling = "བཀངས་རྗེས་རྡོག་བཟོ" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "དགོས" requiredFieldsError = "ངེས་པར་དགོས་པའི་ས་སྒོ་ཚང་མ་སྐོང་རོགས" rescanFields = "ས་ཁོངས་བསྐྱར་ཞིབ" @@ -4795,9 +4863,6 @@ title = "PDF ནས་པར་རིས།" [imageToPdf] tags = "སྒྱུར་བཅོས། ཨམ་གྷི་,jpg, པར་རིས། པར་རིས།" -[infoBanner] -dismiss = "འདོར" - [invite] acceptError = "རྩིས་ཁྲ་བཟོ་མ་ཐུབ་པ།" accountFor = "རྩིས་ཁྲ་བཟོ་རྒྱུ།" @@ -4979,6 +5044,34 @@ uploadSuccess = "ཡར་འཇུག་ལེགས་འགྲུབ!" uploadSuccessMessage = "ཁྱེད་ཀྱི་པར་རིས་སྤོ་ལེན་བྱས་ཟིན།" validating = "Session བདེན་བཤད་བཤེར་བཞིན..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "བཤར་འབེབས་བསྣུན་ནས་པར་རིས་ཡར་འཇུག། པར་རིས་ PDF ལ་རང་འགུལ་བརྗེ་སྒྱུར།" descriptionNoConvert = "ཁ་པར་ཐབས་འཕྲུལ་ནས་པར་རིས་བསྣུན་ནས་ཡར་འཇུག།" @@ -4997,8 +5090,13 @@ title = "ཁ་པར་ནས་ཡར་འཇུག" tags = "ལག་ཆ་སྣ་མང་། སྣ་མང་བཀོལ་སྤྱོད། UI། སྣུན་དྲུག མདུན་གྱི་མཐའ་མཚམས། འབྲེལ་གཏུག་བྱེད་མཁན།" title = "PDF སྣ་མང་ལག་ཆ།" -[navbar] -search = "བཤེར་འཚོལ" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "བདེན་དཔང་དེ་གྲུབ་འབྲས་ཐོབ་མེད། ཁྱོད་ཀྱིས་སྒེའུ་ཁུང་འདི་ཁ་རྒྱག་ནས་བསྐྱར་དུ་ཚོད་ལྟ་བྱེད་ཐུབ།" @@ -5127,6 +5225,7 @@ activeFiles = "Active Files མཐོང་སྣང་གི allTools = "འདི་ནི་ལག་ཆ གྱི་སྒྲོམ་གཞི་ཡིན། དེར་ཁྱེད་ཀྱིས་ཡོད་པའི་PDFལག་ཆ་ཚང་མ་ནས་བལྟ་ཐུབ་པ་དང་འདེམས་ཐུབ།" close = "ཁ་རྒྱག" cropSettings = "ད་ལྟ་ང་ཚོས་ང་ཚོར་སྟོན་ཐོག་དགོས་པའི་ཡིག་ཆ་དེ་འདེམས་སྒྲུག་བྱས་པས་ང་ཚོས་PDFདེ་བསྡུ་རུབ་བྱེད་འདོད་པའི་ས་ཁུལ་དེ་འདེམས་རྒྱུའི་ཆེད་དུ་Cropལག་ཆ་སྒྲིག་བཀོད་བྱེད་ཐུབ།" +dialogLabel = "Onboarding" fileCheckbox = "ཡིག་ཆ་གཅིག་ལ་སྣུན་ན་ལས་སྣོན་ཆེད་དུ་དེ་འདེམས་སྒྲུག་བྱེད། ཁྱེད་ཀྱིས་སྡེ་ཚན་བཀོལ་སྤྱོད་ཀྱི་ཆེད་དུ་ཡིག་ཆ་སྣ་ཚོགས་འདེམས་ཐུབ།" fileReplacement = "བསྒྱུར་བཅོས་བྱས་པའི་ཡིག་ཆ་དེས་ལས་ཀའི་སྟེགས་བུའི་ནང་དུ་ཡིག་ཆ་ངོ་མ་དེ་རང་འགུལ་གྱིས་ཚབ་བྱེད་པ་དང་།" filesButton = "མགྱོགས་མྱུར་འཛུལ་སྤྱོད་ཕྲ་རིང་སྟེང་གི་ ཡིག་ཆ་ མཐེབ་གཞོང་གིས་ཁྱེད་ལ་ལག་ཆ་བཀོལ་སྤྱོད་བྱེད་པར་PDFs ཡར་བསྐུར་ཐུབ།" @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF ནས་PDF/A" tags = "ཡིག་ཚགས། ཡུན་རིང་ཚད་ལྡན་དང་། བསྒྱུར་བཅོས། དངོས་ཟོག་སྲུང་སྐྱོབ། སྲུང་སྐྱོབ།" title = "PDF ནས་PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "པར་འདེབས,ཚད་ལྡན,བརྗེ་སྤོ,བཟོ་ལས,པར་སྔོན་འགྲིལ,མཛོད་སྣོད" title = "PDF ལས་ PDF/X ལ་བརྗེ་བ" @@ -6045,6 +6151,7 @@ small = "བཀོལ་གྲངས 500" xsmall = "བཀོལ་གྲངས 100" [plan.availablePlans] +currency = "Billing currency" subtitle = "ཁྱེད་རང་གི་དགོས་མཁོ་དང་འཚམ་པའི་འཆར་གཞི་འདེམས།" title = "འཆར་གཞི།" @@ -6624,6 +6731,7 @@ reachedTitle = "ཟླ་རེའི་འགྲོ་གྲོན་ཚད་ title = "Stripe སྒོ་མོ་ཁ་ཕྱེ་མ་ཐུབ་" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "རིན་མེད PDFs {{allowance}} ནས་སྤྱད་པ་" capSuffix_other = "རིན་མེད PDFs {{allowance}} ནས་སྤྱད་པ་" eyebrow = "སྒྲིག་གཅོད་ཆས་ཚོད་སྤྱོད་" @@ -6873,6 +6981,32 @@ label = "Jira" description = "སྲིད་ཇུས་འཁོར་སྐབས་ཡིག་ཆ་ཞིག་ཡང་ན་བརྡ་ཐོ་ཞིག་གློག་འཕྲིན་གྱིས་སྐུར།" label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "ལས་སྣོན་བྱས་པའི་ཡིག་ཆ་ Nextcloud ནང་ཉར།" @@ -7077,11 +7211,6 @@ title = "དངོས་གཞི SDKs" beta = "Beta" deprecated = "Deprecated" -[portal.docs.search] -empty = "མཐུན་པའི docs མེད" -placeholder = "Docs འཚོལ" -results = "{{count}} གྲུབ་འབྲས" - [portal.docs.skills] eyebrow = "SKILLS" lead = "ཁྱེད་ཀྱི agent གིས་ལག་ཆ་གཅིག་ལྟར་འབོད་སྤྱོད་བྱེད་པའི་མཉམ་སྒྲིལ་དང་མིང་ཅན་ནུས་པ། skill རེ་རེ་ནི evals མཐུད་པའི་གཏན་འཁེལ་ཅན op chain ཡིན།" @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "ཡིག་ཆ་གང་ཡང་འཚག་ཆས་འདི་དང་མི་མཐུན།" rowActions = "ཕྲེང་བྱ་སྤྱོད" sensitiveLabel = "ཚོར་སླ" -sensitiveTitle = "ཚོར་སླ — ཐོབ་དབང་དགོས" [portal.documents.table.columns] action = "Pipeline / བྱ་སྤྱོད" document = "ཡིག་ཆ" +labels = "Labels" product = "ཐོན་རྫས" status = "གནས་ཚུལ" time = "དུས་ཚོད" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "མཐའ་མར་མཐོང་བ" region = "ས་ཁུལ" status = "གནས་ཚུལ" +target = "Target" version = "Version" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "མཐོང་ངོས་འདིར་མ་བསམ་པ retry = "ཡང་བསྐྱར་ཚོད་ལྟ་བྱོས" title = "ཤོག་ངོས་འདིར་གནད་དོན་ཞིག་བྱུང་།" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "ཕྱིར་ལོག" body = "ཅོག་ངོས་ཉེར་སྤྱོད་ཕབ་ལེན་བྱོས། ཡང་ན server རང་གིས་གནས་སྡོད་བྱོས།" @@ -7380,11 +7560,6 @@ cancel = "དོར་བ" confirm = "ལྡེ་མིག་ཕྱིར་འཐེན" title = "API ལྡེ་མིག་ཕྱིར་འཐེན" -[portal.infrastructure.attestationLabel] -attested = "དཔང་འདོན་བྱས་པ" -inScope = "ཁྱབ་ཁོངས་ནང" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "རིགས་དབྱེ་ལྟར་ཞིབ་བཤེར་དོན་རྐྱེན་འཚག་བྱོས" heading = "ཞིབ་བཤེར་ཉིན་ཐོ" @@ -7456,11 +7631,6 @@ info = "ཆ་འཕྲིན" success = "ལེགས་གྲུབ" warning = "ཉེན་བརྡ" -[portal.infrastructure.certLabel] -certified = "ལག་ཁྱེར་ཐོབ་པ" -inProgress = "ལས་སྒྲུབ་བཞིན" -notStarted = "འགོ་བཙུགས་མེད་པ" - [portal.infrastructure.createKey] cancel = "དོར་བ" createKey = "ལྡེ་མིག་གསར་སྐྲུན" @@ -7474,238 +7644,10 @@ subtitleCreated = "ད་ལྟ་གསང་བ་འདི་འདྲ་བ title = "API ལྡེ་མིག་གསར་སྐྲུན" titleCreated = "ལྡེ་མིག་གསར་སྐྲུན་ཟིན་པ" -[portal.infrastructure.deployLabel] -live = "ད་ལྟ་འཁོར་སྐྱོད" -queued = "གྲལ་དུ་བསྒུགས་པ" -rolledBack = "ཕྱིར་ལོག་བྱས་པ" -rolling = "ཁྱབ་སྤེལ་བྱེད་བཞིན་པ" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "བཀོད་སྤྱོད་བྱེད་མཁན" -environment = "ཁོར་ཡུག" -product = "ཐོན་རྫས" -status = "གནས་ཚུལ" -version = "པར་གཞི" -when = "དུས་ཚོད" - -[portal.infrastructure.deployments.recent] -heading = "ཉེ་ཆར་གྱི་བཀོད་སྤྱོད" -subheading = "ཐོན་རྫས་དང་ཁོར་ཡུག་ཁག་གི་ཁྱབ་སྤེལ་གསར་ཤོས།" - -[portal.infrastructure.deployments.regionColumns] -instances = "དཔེ་ཚན" -latency = "འགོར་འགྱངས" -load = "ཁུར་པོ" -p99 = "P99" -region = "ས་ཁུལ" -status = "གནས་ཚུལ" -throughput = "ཐོན་ཚད" -uptime = "འཁོར་སྐྱོད་དུས་ཡུན" -version = "པར་གཞི" - -[portal.infrastructure.deployments.regions] -heading = "ས་ཁུལ" -subheading = "བཀོད་སྤྱོད་ཟིན་པའི Stirling ས་ཁུལ་རེ་རེའི་དངོས་སྐབས་བདེ་ཐང་ — འགོར་འགྱངས། ཁུར་པོ། ཁྱབ་སྤེལ་པར་གཞི།" - -[portal.infrastructure.deployments.regions.empty] -description = "ཁྱེད་ཀྱི་ལས་ས་མཁོ་སྒྲུབ་ཟིན་རྗེས། བཀོད་སྤྱོད་ཟིན་པའི་ས་ཁུལ་འདིར་མངོན་ཡོང་།" -title = "ས་ཁུལ་གང་ཡང་བཀོད་སྤྱོད་བྱས་མེད" - [portal.infrastructure.keyLabel] active = "ཤུགས་ལྡན" revoked = "ཕྱིར་འཐེན་བྱས་པ" -[portal.infrastructure.modelLabel] -active = "ཤུགས་ལྡན" -degraded = "ཉམས་པ" -disabled = "ནུས་མེད" - -[portal.infrastructure.models] -heading = "མོ་ཌེལ" -msValue = "{{value}} ms" -subheading = "ཁྱེད་ཀྱི་ལས་ས་ཡོངས་སུ་ཡིག་ཆ་ལས་སྣོན་ལ་ནུས་ཤུགས་སྤྲོད་པའི་མོ་ཌེལ་དཀར་ཆག་དང་ལམ་སྟོན།" - -[portal.infrastructure.models.byom] -description = "ས་གནས་ནང་གི་ཡང་ན་རང་སྐྱོང་གི་མོ་ཌེལ་ཞིག་ཐོ་འགོད་བྱས་ནས། གཞི་གྲངས་གནས་སྡོད་ཚད་བཀག་ལྡན་པའི་ལས་སྣོན་ཆེད་ས་ཁུལ་ཞིག་ལ་གཏན་འཛིན་བྱོས།" -title = "རང་གི་མོ་ཌེལ་འཁྱེར་ཡོང་" - -[portal.infrastructure.models.catalogue] -heading = "དཀར་ཆག" -sub = "དངོས་སྐབས་འགོར་འགྱངས་དང་རིན་གོང་ལྡན་པའི། ཁྱེད་ཀྱི་ལས་ས་ལ་སྤྱོད་རུང་བའི་དོ་དམ་མོ་ཌེལ།" -subEnterprise = "དོ་དམ་མོ་ཌེལ། རང་གི་མོ་ཌེལ། ས་གནས་ནང་གི་མོ་ཌེལ་ — ས་ཁུལ་རེ་རེར་གཏན་འཛིན་བྱེད་ནུས་ལྡན།" - -[portal.infrastructure.models.catalogue.empty] -description = "ཁྱེད་ཀྱི་ལས་སའི་དཀར་ཆག་ནང་གི་མོ་ཌེལ་འདིར་མངོན་ཡོང་།" -title = "མོ་ཌེལ་སྤྱོད་རུང་གང་ཡང་མེད" - -[portal.infrastructure.models.columns] -cost = "རིན་གོང" -latency = "འགོར་འགྱངས" -load = "ཁུར་པོ" -model = "མོ་ཌེལ" -status = "གནས་ཚུལ" -type = "རིགས" -version = "པར་གཞི" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/འབོད་ཐེངས" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "ཤུགས་ལྡན་མོ་ཌེལ" -avgLatency = "ཆ་སྙོམས་འགོར་འགྱངས" -included = "ཚུད་པ" -monthlySpend = "ཟླ་རེའི་མོ་ཌེལ་འགྲོ་སོང་" - -[portal.infrastructure.models.routing] -empty = "ལམ་སྟོན་སྒྲིག་སྲོལ་གང་ཡང་སྒྲིག་མེད།" -heading = "ལམ་སྟོན་སྒྲིག་སྲོལ" -sub = "བཀོལ་སྤྱོད་རེ་རེ་གང་གིས་མོ་ཌེལ་གང་སྤྱོད་པ། སྔོན་སྒྲིག་ནི་དེ་ལས་ཞིབ་ཚགས་པའི་སྒྲིག་སྲོལ་མ་འཚམས་སྐབས་སྤྱོད།" -subLocked = "བཀོལ་སྤྱོད་རྣམས་མོ་ཌེལ་ངེས་ཅན་ལ་ལམ་སྟོན་བྱོས — རིན་སྤྲོད་འཆར་གཞི་ཐོག་སྤྱོད་རུང་།" - -[portal.infrastructure.models.routing.lockedBanner] -description = "Pro ལ་རིམ་སྤར་བྱས་ནས་བཀོལ་སྤྱོད་དང་ཡིག་ཆའི་རིགས་རེ་རེ་གང་གིས་མོ་ཌེལ་གང་སྤྱོད་པ་ཚོད་འཛིན་བྱོས།" -title = "མོ་ཌེལ་ལམ་སྟོན་ནི་རིན་སྤྲོད་ཁྱད་ཆོས་ཡིན" - -[portal.infrastructure.models.routingColumns] -default = "སྔོན་སྒྲིག" -docType = "ཡིག་ཆའི་རིགས" -modelForAria = "{{operation}} ཆེད་ཀྱི་མོ་ཌེལ" -operation = "བཀོལ་སྤྱོད" -routedTo = "ལམ་སྟོན་བྱས་ཡུལ" - -[portal.infrastructure.modelTypeLabel] -classification = "དབྱེ་སྒྲིག" -extraction = "འདོན་ལེན" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "ཉམས་པ" -down = "འཁོར་སྐྱོད་མེད་པ" -healthy = "བདེ་ཐང" - -[portal.infrastructure.security.access.byok] -description = "རང་གི KMS ནས་ལྡེ་མིག་ཞིག་མཁོ་འདོན་བྱོས། Stirling གིས་དེས་གསང་སྡོམ་བྱེད་ཀྱང་ད་དུང་ཀློག་ཐུབ།" -label = "རང་གི་ལྡེ་མིག་འཁྱེར་ཡོང་ (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "ལྡེ་མིག་རྣམས་ཁྱེད་ཀྱི KMS ནས་ནམ་ཡང་མི་ཐོན། Stirling གིས་གསང་ཡིག་ཁོ་ན་ཉར།" -label = "རང་གི་ལྡེ་མིག་རང་ཉར་ (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling གིས་གསང་སྡོམ་ལྡེ་མིག་དོ་དམ་བྱེད། སྟབས་བདེ་ཤོས་ — ཁྱེད་ཕྱོགས་ནས་ལྡེ་མིག་ལས་སྒོ་གང་ཡང་མེད།" -label = "Stirling གིས་ཉར་བའི་ལྡེ་མིག" - -[portal.infrastructure.security.accessPolicy] -heading = "ཡིག་ཆའི་འཛུལ་སྤྱོད་སྲིད་ཇུས" -subheading = "འཇགས་གནས་སྐབས་སུ་ལས་སྣོན་བྱས་པའི་ཡིག་ཆ་སུས་གསང་འགྲོལ་བྱེད་ཐུབ་པ་ཚོད་འཛིན་བྱེད།" - -[portal.infrastructure.security.attestations] -heading = "མཐུན་སྒྲིག་དཔང་འཛིན" -noReport = "སྙན་ཐོ་སྤྱོད་རུང་མེད" -subheading = "སྒྲོམ་གཞི་རེ་རེའི་ཞིབ་བཤེར་གནས་བབ། དཔང་འཛིན་ཟིན་པའི་ཚོད་འཛིན་ཐོག་སྙན་ཐོ་སྤྱོད་རུང་།" -viewReport = "སྙན་ཐོ་ལྟ་བ →" - -[portal.infrastructure.security.compliance] -heading = "མཐུན་སྒྲིག" -subheading = "Stirling སྟེགས་བུར་ཁྱབ་པའི་དཔང་འཛིན་དང་ལག་ཁྱེར།" - -[portal.infrastructure.security.empty] -description = "ཁྱེད་ཀྱི་ལས་སའི་བདེ་འཇགས་སྒྲིག་བཀོད་འདིར་མངོན་ཡོང་།" -title = "བདེ་འཇགས་གནས་བབ་སྤྱོད་རུང་མེད" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK སྤྱད་ན། གསང་སྡོམ་ལྡེ་མིག་རྣམས་ཁྱེད་ཀྱི KMS ནས་ནམ་ཡང་མི་ཐོན། Stirling གིས་ཁྱེད་ཀྱིས་དུས་ནམ་ཡང་ཕྱིར་འཐེན་བྱེད་ཐུབ་པའི་གསང་ཡིག་ཁོ་ན་ཉར་ཞིང་ལས་སྣོན་བྱེད།" -title = "Stirling གིས་ཁྱེད་ཀྱི་ཡིག་ཆ་གསང་འགྲོལ་བྱེད་མི་ཐུབ" - -[portal.infrastructure.security.ipAllowlist] -empty = "IP ཁྱབ་ཁོངས་གང་ཡང་སྒྲིག་མེད་ — IP ཚང་མ་ཆོག" -heading = "IP ཆོག་ཐོ" -sub = "API འཛུལ་སྤྱོད་ནི CIDR ཁྱབ་ཁོངས་འདི་དག་ལ་ཚད་བཀག་བྱས་ཡོད།" -subLocked = "ངོ་ཤེས་པའི IP ཁྱབ་ཁོངས་ལ API འཛུལ་སྤྱོད་ཚད་བཀག་བྱོས — རིན་སྤྲོད་འཆར་གཞི་ཐོག་སྤྱོད་རུང་།" - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Pro ལ་རིམ་སྤར་བྱས་ནས API འཛུལ་སྤྱོད་དྲ་རྒྱ་ངེས་ཅན་ལ་ཚད་བཀག་བྱོས།" -title = "IP ཆོག་ཐོ་ནི་རིན་སྤྲོད་ཁྱད་ཆོས་ཡིན" - -[portal.infrastructure.security.ipColumns] -added = "ཁ་སྣོན་བྱས་པ" -addedBy = "ཁ་སྣོན་བྱེད་མཁན" -cidr = "CIDR" -label = "མཚོན་རྟགས" - -[portal.infrastructure.security.keyManagement] -algorithm = "རྩིས་ཐབས" -heading = "གསང་སྡོམ་ལྡེ་མིག་དོ་དམ" -keyId = "ལྡེ་མིག་ངོས་འཛིན་རྟགས" -lastRotated = "མཐའ་མར་བསྐོར་བ" -rotateKey = "ལྡེ་མིག་བསྐོར་སྤོར" -rotationPolicy = "བསྐོར་སྤོར་སྲིད་ཇུས" -subheading = "འཇགས་གནས་སྐབས་ཡིག་ཆ་གསང་སྡོམ་བྱེད་པའི་ལྡེ་མིག་གི་ཉར་དབང་ — སུས་གསང་འགྲོལ་བྱེད་ཐུབ་པ་དང་། ལྡེ་མིག་ཇི་ལྟར་བསྐོར་སྤོར་བྱེད་པ།" - -[portal.infrastructure.security.managedBanner] -description = "རང་གི་ལྡེ་མིག་འཁྱེར་ཡོང་ (BYOK) དང་རང་གི་ལྡེ་མིག་རང་ཉར་ (HYOK) ཉར་དབང་ནི Enterprise ཐོག་སྤྱོད་རུང་། རང་གི KMS ནས་ལྡེ་མིག་མཁོ་འདོན་བྱེད་ཆེད་རིམ་སྤར་བྱོས།" -title = "ཁྱེད་ཀྱི་འཆར་གཞི་ཐོག་ལྡེ་མིག་རྣམས Stirling གིས་དོ་དམ་བྱེད།" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "ཨེ་ཤེ་ཡ་པ་སི་ཕིག" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR གཞི་གྲངས་མཚམས་མངའ" -label = "ཡོ་རོབ་མཉམ་འབྲེལ" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "ཨ་རི" - -[portal.infrastructure.security.residencyHeader] -heading = "གཞི་གྲངས་གནས་སྡོད" -subheading = "ཡིག་ཆ་གང་དུ་ཉར་བ་དང་ལས་སྣོན་བྱེད་པ།" - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} སྤྱད་པ" - -[portal.infrastructure.storage.empty] -description = "མཐུད་ཟིན་པའི་ཉར་མཛོད་དང་སྤྱོད་ཚད་འདིར་མངོན་ཡོང་།" -title = "ཉར་མཛོད་གང་ཡང་སྒྲིག་མེད" - -[portal.infrastructure.storage.lifecycle] -active = "ཤུགས་ལྡན" -activeRange = "0–{{value}}d" -archived = "ཡིག་ཚགས་སུ་ཉར་བ" -coldStorage = "གྲང་ཉར" -deleted = "སུབ་ཟིན་པ" -never = "ནམ་ཡང་མིན" -purged = "གཙང་སེལ་བྱས་པ" - -[portal.infrastructure.storage.providers] -connect = "མཐུད་པ" -connected = "མཐུད་ཟིན་པ" -heading = "མཐུད་ཟིན་པའི་མཁོ་འདོན་པ" -subheading = "ལས་སྣོན་བྱས་པའི་འབྲས་ཆས་འབྲི་ས།" - -[portal.infrastructure.storage.retention] -heading = "ཉར་འཇོག" -subheading = "འཚོ་རིམ་སུབ་པའི་སྔོན་ལ་འབྲས་ཆས་དུས་ཡུན་ག་ཚོད་ཉར་བ།" -windowLabel = "སྔོན་སྒྲིག་ཉར་འཇོག་དུས་ཡུན" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} ཉིན" -days_other = "{{count}} ཉིན" -never = "ནམ་ཡང་མ་སུབ" - -[portal.infrastructure.storage.totalUsage] -heading = "བེད་སྤྱོད་ཁྱོན་བསྡོམས" -progressLabel = "སྤྱད་ཟིན་པའི་གསོག་ཉར" -subheading = "འབྲེལ་མཐུད་བྱས་པའི་མཁོ་སྤྲོད་པ་ཚང་མའི་ནང་སྤྱད་པའི་གསོག་ཉར།" - [portal.infrastructure.tabs] apiKeys = "API ལྡེ་མིག" audit = "རྩིས་བཤེར་ཉིན་ཐོ" @@ -7715,11 +7657,9 @@ security = "བདེ་འཇགས" storage = "གསོག་ཉར" [portal.integrations] -addAnother = "གཞན་ཞིག་ཁ་སྣོན" availableHeading = "སྤྱོད་རུང" comingSoonHeading = "མི་འགྱངས་པར་འཐོན་རྒྱུ" connect = "འབྲེལ་མཐུད" -connectedHeading = "འབྲེལ་མཐུད་ཟིན" connectionCount_one = "{{count}} འབྲེལ་མཐུད" connectionCount_other = "{{count}} འབྲེལ་མཐུད" customApi = "རང་སྒྲིག API" @@ -7737,6 +7677,10 @@ security = "བདེ་འཇགས" signing = "མིང་རྟགས་འགོད་པ" storage = "གསོག་ཉར" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "འབྲེལ་མཐུད་ཟིན" @@ -7767,7 +7711,6 @@ integrations = "མཉམ་འདྲེས" pipelines = "ལས་རིམ་རྒྱུན" policies = "སྲིད་ཇུས" procurement = "ཉོ་སྒྲུབ" -settings = "སྒྲིག་འགོད" sources = "ཁུངས" usage = "བེད་སྤྱོད་དང་རྩིས་འབོར" users = "སྤྱོད་མཁན" @@ -7784,7 +7727,7 @@ title = "ལས་རིམ་རྒྱུན" newPipeline = "ལས་རིམ་རྒྱུན་གསར་པ" [portal.pipelines.builder] -addStep = "ལག་ཆ་ཁ་སྣོན" +activate = "Activate" back = "ལས་རིམ་རྒྱུན་ལ་ཕྱིར་ལོག" cannotFollow = "{{produced}} ལེན་མི་ཐུབ" chooseAccount = "རྩིས་ཐོ་ཞིག་འདེམས" @@ -7792,28 +7735,38 @@ chooseDestination = "དམིགས་ས་ཞིག་འདེམས" chooseOperation = "གོམ་པ་འདིས་ཅི་བྱེད་འདེམས" chooseSource = "ཁུངས་ཤིག་འདེམས" discard = "འགྱུར་བ་དོར" -enabled = "ནུས་སྤེལ་ཟིན" inputs = "ནང་འཇུག" inputSource = "ནང་འཇུག་ཁུངས" inputTrigger = "སྐུལ་རྐྱེན" keepEditing = "རྩོམ་སྒྲིག་མུ་མཐུད" +moreActions = "More actions" needsConfiguring = "སྒྲིག་སྦྱོར་དགོས" -needsUpload = "ཡར་སྤྲོད་བྱས་པའི་ཡིག་ཆ་དགོས" -noSources = "ད་དུང་ཁུངས་གང་ཡང་འབྲེལ་མཐུད་མ་བྱས། དེ་ནང་འཇུག་ལ་སྤྱོད་ཕྱིར་གཅིག་བཟོས།" +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "ཁྱེད་ཀྱི་འཚོལ་བཤེར་དང་མཐུན་པའི་ལག་ཆ་མེད།" -pipelineSettings = "ལས་རིམ་རྒྱུན་སྒྲིག་འགོད" +pause = "Pause" +rename = "Rename pipeline" searchTools = "ལག་ཆ་འཚོལ" -selectToolBody = "ཁྱེད་ཀྱི་ལས་རིམ་རྒྱུན་བཟོ་ཕྱིར་ལག་ཆ་ཞིག་ཁ་སྣོན།" -selectToolTitle = "ད་དུང་ལག་ཆ་མེད" sendToSystem = "མ་ལག་གཞན་ཞིག་ལ་སྐུར" stepsIncompatible = "གོམ་པ་འདི་དག་དེའི་གོམ་པ་སྔོན་མས་བཟོས་པའི་ཐོན་འབྲས་སྟེང་འཁོར་མི་ཐུབ། {{tools}}" stepsNeedSetup = "གོམ་པ་འདི་དག་ཉར་ཚགས་མ་བྱས་གོང་ད་དུང་སྒྲིག་སྦྱོར་དགོས། {{tools}}" -toolSettings = "ལག་ཆ་སྒྲིག་འགོད" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "ངོས་འཛིན་མ་ཐུབ་པའི་བཀོལ་སྤྱོད། རང་འཇགས་ཉར་ཡོད།" unsavedBody = "ཁྱེད་ལ་ཉར་ཚགས་མ་བྱས་པའི་འགྱུར་བ་ཡོད། ཕྱིར་མ་ཐོན་གོང་དེ་དག་ཉར་ཚགས་བྱེད་དམ། ཡང་ན་དོར་རམ།" unsavedTitle = "ཉར་ཚགས་མ་བྱས་པའི་འགྱུར་བ" -uploadUnsupported = "ལས་རིམ་རྒྱུན་ནང་ད་དུང་ཡར་སྤྲོད་བྱས་པའི་ཡིག་ཆ་རྒྱབ་སྐྱོར་མེད་པས། གོམ་པ་འདི་དག་ཉར་ཚགས་བྱེད་མི་ཐུབ། {{tools}}" usesDefaults = "སྔོན་སྒྲིག་སྒྲིག་འགོད་ཀྱིས་འཁོར" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "གོམ་པ་སྔོན་མ་ནས་ཡིག་ཆ་ཚང་མ་ཟླ་སྒྲིལ་བྱེད" @@ -7825,31 +7778,36 @@ undeclared-operation = "གོམ་པ་འདིས་གང་ལེན་ [portal.pipelines.composer] addTool = "ལག་ཆ་ཁ་སྣོན" -cancel = "དོར" -chainEmpty = "ཁྱེད་ཀྱི་ལས་རིམ་རྒྱུན་བཟོ་འགོ་ཚུགས་ཕྱིར་ལག་ཆ་ཞིག་ཁ་སྣོན།" create = "ལས་རིམ་རྒྱུན་བཟོ" +createPaused = "Create paused" editingUnsupported = "རྩོམ་སྒྲིག་ཆེད་ལག་ཆ་འདི་དག་གི་ཞུགས་གྲངས་སྟོན་པ་ད་དུང་རྒྱབ་སྐྱོར་མེད།" -moveDown = "མར་སྤོ" -moveUp = "ཡར་སྤོ" +editSource = "Edit source" name = "མིང" namePlaceholder = "དཔེར་ན། ནག་སུབ་བཤེར་བ" noToolSettings = "ལག་ཆ་འདིར་སྒྲིག་སྦྱོར་བྱ་རུང་གི་སྒྲིག་འགོད་མེད།" -operations_one = "བཀོལ་སྤྱོད ({{count}})" -operations_other = "བཀོལ་སྤྱོད ({{count}})" output = "དམིགས་ས" -removeStep = "བཀོལ་སྤྱོད་སུབ" save = "འགྱུར་བ་ཉར་ཚགས" scheduleEvery = "འཁོར་ཚད" -sources = "ཁུངས" -sourcesLoading = "ཁུངས་འཇུག་བཞིན..." trigger = "སྐུལ་རྐྱེན" triggerManual = "ལག་བཟོ་ཁོ་ན" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "ཉིན" hours = "ཆུ་ཚོད" minutes = "སྐར་མ" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\" སུབ་བམ། འདི་ཕྱིར་ལྡོག་བྱེད་མི་ཐུབ།" cancel = "དོར" @@ -7867,6 +7825,37 @@ connectSource = "ཁུངས་ཤིག་འབྲེལ་མཐུད" description = "ཁྱེད་ཀྱི་ལས་རིམ་རྒྱུན་དང་པོ་བཟོ། དེ་འཁོར་སའི་ཁུངས་འདེམས། བཀོལ་སྤྱོད་ཕྲེང་བར་སྦྲེལ། དེ་ནས་ཐོན་འབྲས་གང་དུ་འགྲོ་བ་འདེམས།" title = "ད་དུང་ལས་རིམ་རྒྱུན་མེད" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "འཁོར་བཞིན" paused = "མཚམས་བཞག" @@ -7879,7 +7868,6 @@ completed_one = "འཁོར་བ་ལེགས་གྲུབ།" completed_other = "འཁོར་བ་{{count}} ཚང་མ་ལེགས་གྲུབ།" empty = "འཁོར་རྒྱུ་མེད། ཁུངས་ནང་སྒྲིག་གཅོད་བྱ་རྒྱུའི་ཡིག་ཆ་མེད།" failed = "འཁོར་བ་ཕམ་སོང་། {{error}}" -historyCleared = "ལོ་རྒྱུས་གཙང་སེལ་བྱས་ཟིན། འཁོར་ཐེངས་རྗེས་མས་ད་ལྟ་ཁུངས་ནང་ཡོད་པ་ཚང་མ་ཡང་བསྐྱར་སྒྲིག་གཅོད་བྱེད།" inFlight = "འཁོར་རྒྱུ་གསར་པ་མེད། སྔོན་གྱི་འཁོར་ཐེངས་ཤིག་ནས་ཡིག་ཆ་ད་དུང་སྒྲིག་གཅོད་བྱེད་བཞིན་ཡོད།" parked_one = "འཁོར་རྒྱུ་མེད། ཡིག་ཆ་{{count}} སྔོན་མར་ཕམ་སོང་ནས་ཟུར་དུ་བཞག་ཡོད། རྒྱུ་རྐྱེན་བཅོས་རྗེས། ཡང་བསྐྱར་ཚོད་ལྟ་བྱ་ཕྱིར་ལོ་རྒྱུས་གཙང་སེལ་བྱོས།" parked_other = "འཁོར་རྒྱུ་མེད། ཡིག་ཆ་{{count}} སྔོན་མར་ཕམ་སོང་ནས་ཟུར་དུ་བཞག་ཡོད། རྒྱུ་རྐྱེན་བཅོས་རྗེས། ཡང་བསྐྱར་ཚོད་ལྟ་བྱ་ཕྱིར་ལོ་རྒྱུས་གཙང་སེལ་བྱོས།" @@ -7895,6 +7883,7 @@ name = "ལས་རིམ་རྒྱུན" sources = "ཁུངས" status = "གནས་ཚུལ" steps = "གོམ་པ" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "ཡིག་ཁུག་ལྟ་སྲུང" @@ -8209,6 +8198,24 @@ label = "Jira དོན་ཚན་ཞིག་ལ་སྦྱར" description = "ལས་སྣོན་བྱས་པའི་ཡིག་ཆ་ཟུར་སྦྱར་ལྟར་སྐུར།" label = "ཡིག་ཆ email སྐུར (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "ལས་སྣོན་བྱས་པའི་ཡིག་ཆ་ཡིག་ཁུག་ཞིག་ནང་འབྲི།" label = "Nextcloud ལ་ཡར་འཇུག" @@ -8683,16 +8690,6 @@ maxed = "མཐོ་ཚད་ལ་སླེབས" subtitle = "ཁྱེད་ཀྱི་རིན་མེད་ཚོད་སྤྱོད {{date}} བར་འགྲོ སྤྱོད་བྱང་མི་དགོས།" title = "ཁེ་ལས་ཚོད་སྤྱོད" -[portal.search] -ariaLabel = "འཚོལ" -placeholder = "Stirling ནང་འཚོལ — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "མགྱོགས་བྱ་སྤྱོད་ཆོག་པ་ཆགས་སྐབས་འདིར་མངོན་ཡོང་།" -noActionsTitle = "མགྱོགས་བྱ་མེད" -noMatches = "\"{{query}}\" ལ་མཐུན་པ་མེད" -noMatchesDescription = "ལྡེ་ཚིག་གཞན་ཞིག་ཚོད་ལྟ་བྱོས་སམ་དཀར་ཆག་ལ་ལྟོས།" - [portal.settings.groups] admin = "དོ་དམ་པ" @@ -8743,10 +8740,6 @@ cancel = "འདོར" confirm = "སུབ" title = "ཁུངས་སུབ་བམ།" -[portal.sources.empty] -description = "ཁྱེད་ཀྱི་སྲིད་ཇུས་ལ་གཞི་གྲངས་འཐེན་ས་ཡོད་པའི་ཆེད་ཉར་ཚགས་གནས་ཤིག་འབྲེལ་མཐུད་བྱོས།" -title = "ད་དུང་ཁུངས་འབྲེལ་མཐུད་བྱས་མེད" - [portal.sources.kpi] inUse = "སྤྱོད་བཞིན" total = "འབྲེལ་མཐུད" @@ -8785,6 +8778,7 @@ unused = "སྤྱོད་མེད" documents = "ཡིག་ཆ" source = "ཁུངས" status = "གནས་ཚུལ" +type = "Type" usedBy = "སྲིད་ཇུས" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "སྲིད་ཇུས་དང་གསང་བ།" [settings.preferences] title = "འདོད་པ།" -[settings.search] -placeholder = "སྒྲིག་འགོད་ཤོག་ངོས་འཚོལ..." - [settings.security] description = "ཁྱེད་རང་གི་རྩིས་ཁྲ་དེ་བདེ་འཇགས་ཡོང་བའི་ཆེད་དུ་གསང་གྲངས་གསར་བརྗེ་བྱེད་དགོས།" title = "ཉེན་སྲུང" @@ -10128,6 +10119,7 @@ last = "ཤོག་ངོས་མཐའ་མ།" maintainRatio = "འགྱུར་ལྡོག་རྒྱུན་འཛིན་གྱི་ཆ་སྙོམས།" next = "ཤོག་ངོས་རྗེས་མ།" noSavedSigs = "ཉར་ཚགས་བྱས་པའི་མཚན་རྟགས་རྙེད་མེད།" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "མི་སྒེར་གྱི་མཚན་རྟགས།" previous = "ཤོག་ངོས་གོང་མ།" redo = "བསྐྱར་དུ་བྱེད།" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "རྒྱབ་ལྗོངས་མེད་པ hint = "ཁྱེད་རང་གི་མིང་རྟགས་བཀོད་པའི་PNG ཡང་ན་JPG པར་རིས་ཤིག་བླངས།" label = "Upload མཚན་རྟགས་པར་རིས།" placeholder = "པར་རིས་ཡིག་ཆ་འདེམས།" +previewAlt = "Current image signature" processing = "ལས་སྣོན་པར་རིས་..." removeBackground = "རྒྱབ་ཤ་དཀར་པོའི་རྒྱབ་ལྗོངས་མེད་པར་བཟོས།" @@ -10179,6 +10172,17 @@ saved = "གོང་དུ་ཉར་ཚགས་བྱས་པའི་མ text = "གོང་དུ་ཁྱེད་ཀྱི་མིང་བླུགས་རྗེས་PDFསྟེང་གང་དུ་ཡང་སྣུན་ནས་མིང་རྟགས་བཀོད་དགོས།" title = "མཚན་རྟགས་ཁ་སྣོན་བྱེད་ཐབས།" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "སྒུལ་བདེའི་མཚན་རྟགས།" pause = "བར་མཚམས་བཞག་པ།" @@ -10625,6 +10629,23 @@ title = "སར་བར་ལ་ཡར་བསྐུར།" updateButton = "སར་བར་ནང་གསར་སྒྱུར།" uploadButton = "སར་བར་ལ་ཡར་བསྐུར།" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "ཕྱིར་ལོག" pdfTools = "PDF ལག་ཆ།" placeholder = "འགོ་འཛུགས་བྱེད་པའི་ལག་ཆ་ཞིག་འདེམས།" premiumFeature = "གོང་ཚད་མཐོ་བའི་ཁྱད་ཆོས།" -searchTools = "ལག་ཆ་འཚོལ།" toolsHeader = "ལག་ཆས" viewAllTools = "ལག་ཆས་ཡོངས་ལྟ" @@ -10822,6 +10842,9 @@ removal = "བཏོན་པ།" signing = "མིང་རྟགས་བཀོད་པ།" verification = "བདེན་དཔང་།" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "ལག་ཆ་གང་ཡང་མཐོང་མ་བྱུང་།" noTools = "ལག་ཆ་མེད་པ།" @@ -10979,6 +11002,13 @@ approver = "སྲིད་ཇུས་ཆོག་མཆན་སྤྲོད editor = "རྩོམ་སྒྲིག་པ།" processor = "ལས་སྣོན་པ།" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "{{email}} ལ་བསྐུར་བའི་གདན་ཞུ་མེད་པར་བཟོ་དགོས་སམ། ཁོང་ཚོས་ད་ལྟའི་སྦྲེལ་ཐག་སྤྱད་ནས་ཞུགས་མི་ཐུབ།" cancelInviteTitle = "གདན་ཞུ་མེད་པར་བཟོ།" @@ -10998,10 +11028,8 @@ title = "ད་དུང་ཁོངས་མི་མེད།" addToTeam = "ཚོགས་ཆུང་ལ་སྣོན།" guestCount = "མགྲོན་པོ་{{count}}" guests = "མགྲོན་པོ།" -guestsDesc = "ཕྱི་ཁུལ་མཉམ་ལས་པ། ཁྱེད་ཀྱིས་མཉམ་སྤྱོད་བྱས་པའི་ཁྱབ་ཁོངས་ནང་ཚད་བཀག་ཡོད། རྩོམ་སྒྲིག་པ་ཁོ་ན།" ledBy = "{{owner}} ཡིས་འགོ་ཁྲིད་བྱས།" org = "རྩ་འཛུགས།" -orgDesc = "རྩ་འཛུགས་ཡོངས་ཀྱི་དབང་ཚད་དང་སྲིད་ཇུས་ཆོག་མཆན་ཡོད་པའི་བདག་པོ།" owners = "བདག་པོ་{{count}}" team = "{{name}} ཚོགས་ཆུང་།" teamMeta = "མི་{{count}}" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "{{who}} ཡིས་གདན་ཞུ་བྱས།" cancel = "མེད་པར་བཟོ།" -count = "ངང་སྒུག་{{count}}" -desc = "ད་དུང་མ་ཞུགས་པའི་གདན་ཞུ་བྱས་པའི་མི་སྣ། ཁོང་ཚོས་དང་ལེན་མ་བྱས་བར་སྡོད་ས་ཞིག་ཉར་ཡོད།" expiresInDays_one = "ཉིན་{{count}} ནང་དུས་ཚོད་ཡོལ།" expiresInDays_other = "ཉིན་{{count}} ནང་དུས་ཚོད་ཡོལ།" expiresToday = "དེ་རིང་དུས་ཚོད་ཡོལ།" title = "ངང་སྒུག་གདན་ཞུ།" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "backend ལ་འབྲེལ་སྐབས་གནད་དོན་ཞིག་བྱུང་ངམ། ཡང་ན་ཁྱེད་ལ་འཛུལ་སྤྱོད་དབང་ཚད་མེད། ཡང་བསྐྱར་ཚོད་ལྟ་བྱོས།" title = "ཁོངས་མི་འཇུག་མ་ཐུབ།" @@ -11237,12 +11267,15 @@ searchPlaceholder = "ཟུར་སྣོན་འཚོལ" title = "ཟུར་སྣོན" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "དཔེ་རྟགས་ཁ་བྱང" bookmarkTitleRequired = "དཔེ་རྟགས་ཁ་བྱང་དགོས་ངེས་ཡིན།" closeSidebar = "དཔེ་རྟགས་ཟུར་སྒྲོམ་ཁ་རྒྱོབ" collapseAll = "དཔེ་རྟགས་ཚང་མ་བསྡུ" +empty = "No bookmarks in this document" expandAll = "དཔེ་རྟགས་ཚང་མ་རྒྱ་སྐྱེད" searchPlaceholder = "དཔེ་རྟགས་འཚོལ" +title = "Bookmarks" [viewer.comments] addComment = "བསམ་བཀོད་ཁ་སྣོན" @@ -11264,10 +11297,12 @@ locateAnnotation = "ཡིག་ཆ་ནང་ས་གནས་འཚོལ" moreActions = "བྱ་བ་མང་བ" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "ཤོག་ངོས་ {{page}}" placingHint = "ཤོག་ངོས་ཞིག་ལ་གནས་བཞག་བྱས་ནས་མནན… (འདོར)" removeCommentOnly = "བསམ་བཀོད་ཙམ་སུབ" saveReply = "ལན་སློག་སྲུང་" +searchPlaceholder = "Search comments" title = "བསམ་བཀོད" typeComment = "བསམ་བཀོད" typeInsertText = "ཡིག་གེ་འཇུག" @@ -11290,6 +11325,7 @@ unsavedDesc = "ཁྱེད་ལ་མ་ཉར་པའི་བཟོ་བ closeSidebar = "རིམ་པ་ཟུར་སྒྲོམ་ཁ་རྒྱོབ" hideAll = "རིམ་པ་ཚང་མ་སྦས" showAll = "རིམ་པ་ཚང་མ་སྟོན" +title = "Layers" [viewer.link] delete = "འབྲེལ་མཐུད་སུབ" @@ -11646,6 +11682,7 @@ exitRedaction = "སྦ་བཅོས་ཐབས་ལམ་ནས་ཕྱི exportAll = "PDF ཕྱིར་འདྲེན" exportSelected = "འདེམས་ཟིན་པའི་ཤོག་ངོས་ཕྱིར་འདྲེན" formFill = "རྣམ་གཞག་ཁ་སྐོང་" +hideToolbar = "Hide toolbar" multiTool = "སྣ་ཚོགས་ལག་ཆས" panMode = "སྤོ་བཤུད་ཐབས་ལམ" print = "PDF དཔར་བསྐྲུན" @@ -11666,6 +11703,7 @@ selectAll = "ཚང་མ་འདེམས" selectByNumber = "ཤོག་ངོས་གྲངས་ཀྱིས་འདེམས" selectLanguage = "སྐད་ཡིག་འདེམས" share = "མཉམ་སྤྱོད" +showToolbar = "Show toolbar" toggleAnnotations = "མཆན་བཀོད་མཐོང་རུང་བ་བསྒྱུར" toggleAttachments = "ཟུང་འདྲེན་བསྒྱུར" toggleBookmarks = "མཚོན་རྟ་བསྒྱུར" diff --git a/frontend/editor/public/locales/ca-CA/translation.toml b/frontend/editor/public/locales/ca-CA/translation.toml index 8b26bc7f3d..3a1ccb7e35 100644 --- a/frontend/editor/public/locales/ca-CA/translation.toml +++ b/frontend/editor/public/locales/ca-CA/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabet" apply = "Aplica" applyAndContinue = "Desa i surt" -areYouSure = "Segur que vols sortir?" back = "Enrere" black = "Negre" blue = "Blau" @@ -106,6 +105,7 @@ undoSuccess = "Operació desfeta correctament" unknown = "Desconegut" unpin = "Desfixa el fitxer (substitueix-lo després d'executar l'eina)" unsavedChanges = "Tens canvis sense desar al teu PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Canvis sense desar" unsupported = "No compatible" username = "Usuari" @@ -1879,6 +1879,9 @@ width = "Amplada" [app] description = "L'alternativa gratuïta a Adobe Acrobat (més de 10 M de descàrregues)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Converteix a PDF/A-3b" convertToPdfA3bDescription = "Crea un PDF d'arxiu amb fitxers adjunts incrustats" @@ -2933,12 +2936,16 @@ text = "Per fer que aquests permisos no es puguin canviar, utilitzeu l’eina Af [chat.actions] copy = "Copia el missatge" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opcions de l'agent Stirling" +assistant = "AI Assistant" clearChat = "Neteja el xat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Aquesta operació utilitzarà els vostres crèdits del núvol" tooltip = "Tria el color de la pantalla" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Tria el color" [common] +actions = "Actions" back = "Enrere" cancel = "Cancel·la" close = "Tanca" +codeSample = "Code sample" collapse = "Replega" confirm = "Confirma" continue = "Continua" copied = "Copiat!" copy = "Copia" done = "Fet" +enterValue = "Enter value" error = "Error" expand = "Desplega" loading = "Carregant..." next = "Següent" +open = "Open" preview = "Vista prèvia" previous = "Anterior" refresh = "Actualitza" remaining = "Restant" retry = "Reintenta" save = "Desa" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Esborra la selecció" @@ -3185,6 +3199,7 @@ title = "Mètode de compressió" [compress.settings] desiredSize = "Mida de fitxer desitjada" desiredSizePlaceholder = "Introdueix la mida" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "La compressió és una manera fàcil de reduir la mida del fitxer. Trieu Mida de fitxer per introduir una mida objectiu i que ajustem la qualitat per vosaltres. Trieu Qualitat per establir manualment la intensitat de la compressió." @@ -3387,6 +3402,24 @@ pdfOptions = "Opcions de PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Selecciona primer un format d'origen" settings = "Opcions" single = "Únic" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Cerca..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Edita" @@ -3824,11 +3861,13 @@ shareSelected = "Comparteix els seleccionats" sharing = "Compartició" showAll = "Mostra-ho tot" showHistory = "Mostra l'historial" +sortBy = "Sort files" sortByDate = "Ordena per data" sortByName = "Ordena per nom" sortBySize = "Ordena per mida" storage = "Emmagatzematge" storageState = "Emmagatzematge" +storageUsed = "Storage used" synced = "Sincronitzat" title = "Puja fitxers PDF" toolChain = "Eines aplicades" @@ -3874,8 +3913,12 @@ addFiles = "Afegeix fitxers" addingFiles = "S'estan afegint fitxers…" collapse = "Redueix la barra lateral" customizeGroups = "Personalitza els grups" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Obre fitxers per començar" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Amplia la barra lateral" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive no està configurat" @@ -3888,15 +3931,17 @@ openFromComputer = "Obre des de l’ordinador" openSettings = "Obre la configuració" other = "Altres" recent = "Recents" -search = "Cerca" -searchPlaceholder = "Cerca fitxers..." viewAll = "Mostra tots els {{count}} fitxers" [fileSidebar.fileItem] closeViewer = "Tanca el visor" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Obre al visor" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Restableix els valors predeterminats" subtitle = "Agrupa els teus fitxers en categories principals. Afegeix etiquetes existents o noves a una categoria, canvia'n el nom o crea'n una de pròpia. Els fitxers que no pertanyen a cap de les teves categories apareixen a “Altres”." title = "Categories de la barra lateral" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Afegeix a l'espai de treball" addToWorkspaceCount = "Afegeix {{count}} a l'espai de treball" @@ -3918,7 +3971,6 @@ backToMyFiles = "Torna a Els meus fitxers" breadcrumbs = "Ruta de la carpeta" cancel = "Cancel·la" classification = "Classificació" -clearSearch = "Neteja la cerca" clearSelection = "Neteja la selecció" closeDetails = "Tanca els detalls" create = "Crea" @@ -3950,6 +4002,7 @@ downloadAll = "Descarrega-ho tot" downloadVersion = "Descarrega aquesta versió" dropOverlay = "Deixa anar fitxers per pujar-los" dropOverlaySub = "Els fitxers comencen a Local. Utilitza 'Mou a' o 'Desa al núvol' per organitzar-los en una carpeta." +duplicate = "Duplicate" file = "Fitxer" fileInfo = "File info" fileMenu = "Accions del fitxer" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Canvia la mida de l'arbre de carpetes (tecles de fletxa, Maj save = "Desa" saveToServer = "Desa al servidor" saveToServerDisabledHint = "Desar al servidor no està habilitat en aquest servidor. Demana a l'administrador que ho activi." -search = "Cerca" -searchPlaceholder = "Cerca en aquesta carpeta i subcarpetes" selectAll = "Selecciona-ho tot" selectAllHint = "Fes clic per seleccionar-ho tot. Consell: mantén Ctrl (o Cmd) per afegir fitxers d'un en un, Maj per seleccionar un interval." selectedCount = "{{count}} seleccionats" @@ -4044,6 +4095,10 @@ title = "Encara no hi ha fitxers al núvol" hint = "Els fitxers desats sense pujar es queden aquí. Deixa anar un fitxer per afegir-ne un." title = "Sense fitxers només locals" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Els fitxers que obris o editis apareixeran aquí." title = "Encara no s'ha modificat res" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "No s'ha pogut suprimir la carpeta." deleteFolderFailedDetail = "No s'ha pogut suprimir la carpeta: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "No s'ha pogut actualitzar l'aparença de la carpeta." folderAppearanceFailedDetail = "No s'ha pogut actualitzar l'aparença de la carpeta: {{message}}" moveFilesFailed = "No s'han pogut moure els fitxers." @@ -4113,6 +4170,11 @@ localHint = "Només emmagatzemat en aquest navegador" shared = "Compartit" sharedHint = "Compartit amb tu mitjançant enllaç" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Més antic primer" modifiedDesc = "Més recent primer" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "No hi ha fitxers disponibles a l'emmagatzematge. Obre al open = "Obre" openFile = "Obre fitxer" openFiles = "Obre fitxers" +selectFile = "Select {{name}}" selectFromStorage = "Seleccioneu de l'emmagatzematge" upload = "Puja" uploadFile = "Puja fitxer" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Tot desat" +analyzingFields = "Analysing form fields..." extractCsvError = "No s'ha pogut extreure el CSV" extractXlsxError = "No s'ha pogut extreure l'XLSX" +filled = "filled" flattenAfterFilling = "Aplana després d'emplenar" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obl." requiredFieldsError = "Emplena tots els camps obligatoris" rescanFields = "Torna a analitzar els camps" @@ -4795,9 +4863,6 @@ title = "Imatge a PDF" [imageToPdf] tags = "conversió,img,jpg,imatge,foto" -[infoBanner] -dismiss = "Descarta" - [invite] acceptError = "No s’ha pogut crear el compte" accountFor = "Creant compte per a" @@ -4979,6 +5044,34 @@ uploadSuccess = "Pujada correcta!" uploadSuccessMessage = "Les imatges s'han transferit." validating = "Validant la sessió..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Escaneja per pujar fotos. Les imatges es converteixen automàticament a PDF." descriptionNoConvert = "Escaneja per pujar fotos des del dispositiu mòbil." @@ -4997,8 +5090,13 @@ title = "Puja des del mòbil" tags = "Eina Multifunció,operacions múltiples,UI,clic i arrossega,costat frontal,client" title = "Eina multifunció de PDF" -[navbar] -search = "Cerca" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "L'autenticació no ha estat correcta. Pots tancar aquesta finestra i tornar-ho a provar." @@ -5127,6 +5225,7 @@ activeFiles = "La vista de Fitxers actius mostra tots els PDF q allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Tanca" cropSettings = "Ara que hem seleccionat el fitxer que volem retallar, podem configurar l’eina Retalla per triar l’àrea a la qual volem retallar el PDF." +dialogLabel = "Onboarding" fileCheckbox = "Si feu clic en un dels fitxers, es selecciona per al processament. Podeu seleccionar diversos fitxers per a operacions en lot." fileReplacement = "El fitxer modificat substituirà automàticament l’original a la Taula de treball, cosa que us permetrà passar-lo fàcilment per més eines." filesButton = "El botó de Fitxers a la barra d’accés ràpid us permet pujar PDF per utilitzar les eines." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF a PDF/A" tags = "arxiu,llarg termini,estàndard,conversió,emmagatzematge,preservació" title = "PDF a PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "impressió,estàndard,conversió,producció,preimpressió,arxiu" title = "PDF a PDF/X" @@ -6045,6 +6151,7 @@ small = "500 crèdits" xsmall = "100 crèdits" [plan.availablePlans] +currency = "Billing currency" subtitle = "Trieu el pla que s’adapta a les vostres necessitats" title = "Plans disponibles" @@ -6624,6 +6731,7 @@ reachedTitle = "S'ha assolit el límit de despesa mensual" title = "No s'ha pogut obrir el portal de Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "de {{allowance}} PDF gratuïts utilitzats" capSuffix_other = "de {{allowance}} PDF gratuïts utilitzats" eyebrow = "Prova de Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Envia un document o una notificació per correu electrònic quan s'executi una política." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Arxiva documents processats a Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDK oficials" beta = "Beta" deprecated = "Obsolet" -[portal.docs.search] -empty = "No hi ha documentació coincident" -placeholder = "Cerca a la documentació" -results = "{{count}} resultats" - [portal.docs.skills] eyebrow = "HABILITATS" lead = "Capacitats incloses i amb nom que el teu agent invoca com una sola eina. Cada habilitat és una cadena d'operacions determinista amb avaluacions associades." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Cap document coincideix amb aquest filtre." rowActions = "Accions de fila" sensitiveLabel = "Sensible" -sensitiveTitle = "Sensible — cal accés" [portal.documents.table.columns] action = "Canalització / Acció" document = "Document" +labels = "Labels" product = "Producte" status = "Estat" time = "Hora" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Vist per última vegada" region = "Regió" status = "Estat" +target = "Target" version = "Versió" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Aquesta vista ha trobat un error inesperat. Torna-ho a provar o t retry = "Torna-ho a provar" title = "Alguna cosa ha anat malament en aquesta pàgina" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Enrere" body = "Baixa l'aplicació d'escriptori o autoallotja el servidor." @@ -7380,11 +7560,6 @@ cancel = "Cancel·la" confirm = "Revoca la clau" title = "Revoca la clau API" -[portal.infrastructure.attestationLabel] -attested = "Atestat" -inScope = "Dins de l'abast" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtra esdeveniments d'auditoria per categoria" heading = "Registres d'auditoria" @@ -7456,11 +7631,6 @@ info = "Info" success = "Correcte" warning = "Advertiment" -[portal.infrastructure.certLabel] -certified = "Certificat" -inProgress = "En curs" -notStarted = "No iniciat" - [portal.infrastructure.createKey] cancel = "Cancel·la" createKey = "Crea una clau" @@ -7474,238 +7644,10 @@ subtitleCreated = "Copia aquest secret ara — no es tornarà a mostrar." title = "Crea una clau API" titleCreated = "Clau creada" -[portal.infrastructure.deployLabel] -live = "En directe" -queued = "A la cua" -rolledBack = "Revertit" -rolling = "Desplegant" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Desplegat per" -environment = "Entorn" -product = "Producte" -status = "Estat" -version = "Versió" -when = "Quan" - -[portal.infrastructure.deployments.recent] -heading = "Desplegaments recents" -subheading = "Els darrers desplegaments en productes i entorns." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instàncies" -latency = "Latència" -load = "Càrrega" -p99 = "P99" -region = "Regió" -status = "Estat" -throughput = "Rendiment" -uptime = "Temps d'activitat" -version = "Versió" - -[portal.infrastructure.deployments.regions] -heading = "Regions" -subheading = "Salut en directe de cada regió Stirling desplegada — latència, càrrega i versió del desplegament." - -[portal.infrastructure.deployments.regions.empty] -description = "Les regions desplegades apareixen aquí un cop s'hagi proveït el teu espai de treball." -title = "No hi ha regions desplegades" - [portal.infrastructure.keyLabel] active = "Activa" revoked = "Revocada" -[portal.infrastructure.modelLabel] -active = "Actiu" -degraded = "Degradat" -disabled = "Desactivat" - -[portal.infrastructure.models] -heading = "Models" -msValue = "{{value}} ms" -subheading = "El catàleg de models i l'encaminament que impulsen el processament de documents al teu espai de treball." - -[portal.infrastructure.models.byom] -description = "Registra un model on-prem o self-hosted i fixa'l a una regió per a processament subjecte a residència de dades." -title = "Porta el teu propi model" - -[portal.infrastructure.models.catalogue] -heading = "Catàleg" -sub = "Models gestionats disponibles al teu espai de treball, amb latència i cost en directe." -subEnterprise = "Models gestionats, porta-el-teu-propi i on-prem — amb fixació per regió disponible." - -[portal.infrastructure.models.catalogue.empty] -description = "Els models del catàleg del teu espai de treball apareixen aquí." -title = "No hi ha models disponibles" - -[portal.infrastructure.models.columns] -cost = "Cost" -latency = "Latència" -load = "Càrrega" -model = "Model" -status = "Estat" -type = "Tipus" -version = "Versió" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/crida" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Models actius" -avgLatency = "Latència mitjana" -included = "Inclòs" -monthlySpend = "Despesa mensual en models" - -[portal.infrastructure.models.routing] -empty = "No hi ha regles d'encaminament configurades." -heading = "Regles d'encaminament" -sub = "Quin model gestiona cada operació. El valor predeterminat s'aplica quan no coincideix cap regla més específica." -subLocked = "Encamina operacions a models específics — disponible en plans de pagament." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Actualitza a Pro per controlar quin model gestiona cada operació i tipus de document." -title = "L'encaminament de models és una funció de pagament" - -[portal.infrastructure.models.routingColumns] -default = "Predeterminat" -docType = "Tipus de document" -modelForAria = "Model per a {{operation}}" -operation = "Operació" -routedTo = "Encaminat a" - -[portal.infrastructure.modelTypeLabel] -classification = "Classificació" -extraction = "Extracció" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degradat" -down = "Caigut" -healthy = "Correcte" - -[portal.infrastructure.security.access.byok] -description = "Proporciona una clau del teu propi KMS. Stirling hi xifra però encara pot llegir." -label = "Porta la teva pròpia clau (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Les claus no surten mai del teu KMS. Stirling només conserva text xifrat." -label = "Conserva la teva pròpia clau (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling gestiona les claus de xifratge. El més senzill — zero operacions de claus per part teva." -label = "Claus conservades per Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Política d'accés a documents" -subheading = "Controla qui pot desxifrar documents processats en repòs." - -[portal.infrastructure.security.attestations] -heading = "Atestacions de compliment" -noReport = "No hi ha cap informe disponible" -subheading = "Postura d'auditoria per marc, amb informes disponibles sobre controls atestats." -viewReport = "Mostra l'informe →" - -[portal.infrastructure.security.compliance] -heading = "Compliment" -subheading = "Atestacions i certificacions que cobreixen la plataforma Stirling." - -[portal.infrastructure.security.empty] -description = "La configuració de seguretat del teu espai de treball apareixerà aquí." -title = "Postura de seguretat no disponible" - -[portal.infrastructure.security.hyokBanner] -description = "Amb HYOK, les claus de xifratge no surten mai del teu KMS. Stirling emmagatzema i processa només text xifrat que pots revocar en qualsevol moment." -title = "Stirling no pot desxifrar els teus documents" - -[portal.infrastructure.security.ipAllowlist] -empty = "No hi ha intervals IP configurats — es permeten totes les IP." -heading = "Llista d'IP permeses" -sub = "L'accés API està restringit a aquests intervals CIDR." -subLocked = "Restringeix l'accés API a intervals IP coneguts — disponible en plans de pagament." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Actualitza a Pro per restringir l'accés API a xarxes específiques." -title = "La llista d'IP permeses és una funció de pagament" - -[portal.infrastructure.security.ipColumns] -added = "Afegit" -addedBy = "Afegit per" -cidr = "CIDR" -label = "Etiqueta" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorisme" -heading = "Gestió de claus de xifratge" -keyId = "Identificador de clau" -lastRotated = "Rotada per última vegada" -rotateKey = "Rota la clau" -rotationPolicy = "Política de rotació" -subheading = "Custòdia de les claus que xifren documents en repòs — qui pot desxifrar i com roten les claus." - -[portal.infrastructure.security.managedBanner] -description = "La custòdia porta-la-teva-pròpia-clau (BYOK) i conserva-la-teva-pròpia-clau (HYOK) està disponible a Enterprise. Actualitza per proporcionar claus del teu propi KMS." -title = "Les claus les gestiona Stirling al teu pla" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Àsia-Pacífic" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · límit de dades GDPR" -label = "Unió Europea" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Estats Units" - -[portal.infrastructure.security.residencyHeader] -heading = "Residència de dades" -subheading = "On s'emmagatzemen i es processen els documents." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} utilitzat" - -[portal.infrastructure.storage.empty] -description = "L'emmagatzematge connectat i l'ús apareixen aquí." -title = "No hi ha emmagatzematge configurat" - -[portal.infrastructure.storage.lifecycle] -active = "Actiu" -activeRange = "0–{{value}}d" -archived = "Arxivat" -coldStorage = "emmagatzematge fred" -deleted = "Eliminat" -never = "mai" -purged = "purgat" - -[portal.infrastructure.storage.providers] -connect = "Connecta" -connected = "Connectat" -heading = "Proveïdors connectats" -subheading = "On s'escriuen els artefactes processats." - -[portal.infrastructure.storage.retention] -heading = "Retenció" -subheading = "Quant de temps es conserven els artefactes abans de l'eliminació del cicle de vida." -windowLabel = "Finestra de retenció predeterminada" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dia" -days_other = "{{count}} dies" -never = "No eliminis mai" - -[portal.infrastructure.storage.totalUsage] -heading = "Ús total" -progressLabel = "Emmagatzematge utilitzat" -subheading = "Emmagatzematge consumit a tots els proveïdors connectats." - [portal.infrastructure.tabs] apiKeys = "Claus API" audit = "Registres d'auditoria" @@ -7715,11 +7657,9 @@ security = "Seguretat" storage = "Emmagatzematge" [portal.integrations] -addAnother = "Afegeix-ne una altra" availableHeading = "Disponibles" comingSoonHeading = "Properament" connect = "Connecta" -connectedHeading = "Connectades" connectionCount_one = "{{count}} connexió" connectionCount_other = "{{count}} connexions" customApi = "API personalitzada" @@ -7737,6 +7677,10 @@ security = "Seguretat" signing = "Signatura" storage = "Emmagatzematge" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Connectada" @@ -7767,7 +7711,6 @@ integrations = "Integracions" pipelines = "Canalitzacions" policies = "Polítiques" procurement = "Compres" -settings = "Configuració" sources = "Fonts" usage = "Ús i facturació" users = "Usuaris" @@ -7784,7 +7727,7 @@ title = "Canalitzacions" newPipeline = "Canalització nova" [portal.pipelines.builder] -addStep = "Afegeix una eina" +activate = "Activate" back = "Torna a les canalitzacions" cannotFollow = "No es pot prendre {{produced}}" chooseAccount = "Tria un compte" @@ -7792,28 +7735,38 @@ chooseDestination = "Tria una destinació" chooseOperation = "Tria què fa aquest pas" chooseSource = "Tria una font" discard = "Descarta els canvis" -enabled = "Activat" inputs = "Entrada" inputSource = "Font d'entrada" inputTrigger = "Activador" keepEditing = "Continua editant" +moreActions = "More actions" needsConfiguring = "Cal configurar-ho" -needsUpload = "Cal un fitxer pujat" -noSources = "Encara no hi ha cap font connectada. Crea'n una per utilitzar-la com a entrada." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Cap eina coincideix amb la cerca." -pipelineSettings = "Configuració de la canalització" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Cerca eines" -selectToolBody = "Afegeix una eina per construir la teva canalització." -selectToolTitle = "Encara no hi ha eines" sendToSystem = "Envia a un altre sistema" stepsIncompatible = "Aquests passos no es poden executar amb el que produeix el pas anterior: {{tools}}." stepsNeedSetup = "Aquests passos encara s'han de configurar abans de desar: {{tools}}." -toolSettings = "Configuració de l'eina" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Operació no reconeguda, conservada tal com està." unsavedBody = "Tens canvis sense desar. Desa'ls abans de sortir o els vols descartar?" unsavedTitle = "Canvis sense desar" -uploadUnsupported = "Els fitxers pujats encara no són compatibles amb les canalitzacions, així que aquests passos no es poden desar: {{tools}}." usesDefaults = "S'executa amb la configuració predeterminada" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combina tots els fitxers del pas anterior" @@ -7825,31 +7778,36 @@ undeclared-operation = "No es pot comprovar què accepta aquest pas" [portal.pipelines.composer] addTool = "Afegeix una eina" -cancel = "Cancel·la" -chainEmpty = "Afegeix una eina per començar a construir la teva canalització." create = "Crea una canalització" +createPaused = "Create paused" editingUnsupported = "Encara no és compatible mostrar aquests paràmetres d'eina per editar-los." -moveDown = "Mou avall" -moveUp = "Mou amunt" +editSource = "Edit source" name = "Nom" namePlaceholder = "p. ex. Revisió de redacció" noToolSettings = "Aquesta eina no té configuració ajustable." -operations_one = "Operació ({{count}})" -operations_other = "Operacions ({{count}})" output = "Sortida" -removeStep = "Elimina l'operació" save = "Desa els canvis" scheduleEvery = "Executa cada" -sources = "Fonts" -sourcesLoading = "Carregant fonts..." trigger = "Disparador" triggerManual = "Només manual" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dies" hours = "hores" minutes = "minuts" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Vols eliminar \"{{name}}\"? Això no es pot desfer." cancel = "Cancel·la" @@ -7867,6 +7825,37 @@ connectSource = "Connecta una font" description = "Crea la teva primera canalització: tria les fonts sobre les quals s'executa, encadena les operacions i tria on va la sortida." title = "Encara no hi ha canalitzacions" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Actives" paused = "En pausa" @@ -7879,7 +7868,6 @@ completed_one = "Execució completada." completed_other = "Totes les {{count}} execucions completades." empty = "No hi ha res per executar: les fonts no tenien documents per processar." failed = "L'execució ha fallat: {{error}}" -historyCleared = "Historial esborrat. La propera execució tornarà a processar tot el que hi hagi actualment a les fonts." inFlight = "No hi ha res nou per executar: encara s'estan processant documents d'una execució anterior." parked_one = "No hi ha res per executar: {{count}} document ha fallat anteriorment i està aparcat. Corregeix-ne la causa i després esborra l'historial per tornar-ho a provar." parked_other = "No hi ha res per executar: {{count}} documents han fallat anteriorment i estan aparcats. Corregeix-ne la causa i després esborra l'historial per tornar-ho a provar." @@ -7895,6 +7883,7 @@ name = "Canalització" sources = "Fonts" status = "Estat" steps = "Passos" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Supervisió de carpeta" @@ -8209,6 +8198,24 @@ label = "Adjunta a una incidència de Jira" description = "Envia el document processat com a fitxer adjunt." label = "Envia el document per correu electrònic (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Escriu el document processat en una carpeta." label = "Puja a Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Límit assolit" subtitle = "La teva prova gratuïta dura fins al {{date}}. No cal targeta." title = "Prova Enterprise" -[portal.search] -ariaLabel = "Cerca" -placeholder = "Cerca a Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Les accions ràpides apareixeran aquí quan estiguin disponibles." -noActionsTitle = "No hi ha accions ràpides" -noMatches = "No hi ha coincidències per a \"{{query}}\"" -noMatchesDescription = "Prova una paraula clau diferent o navega pel catàleg." - [portal.settings.groups] admin = "Administració" @@ -8743,10 +8740,6 @@ cancel = "Cancel·la" confirm = "Suprimeix" title = "Vols suprimir la font?" -[portal.sources.empty] -description = "Connecta una carpeta (i, aviat, emmagatzematge al núvol) perquè les teves polítiques tinguin un lloc d'on obtenir documents." -title = "Encara no hi ha fonts connectades" - [portal.sources.kpi] inUse = "En ús" total = "Connexions" @@ -8785,6 +8778,7 @@ unused = "Sense utilitzar" documents = "Documents" source = "Font" status = "Estat" +type = "Type" usedBy = "Polítiques" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Polítiques i privadesa" [settings.preferences] title = "Preferències" -[settings.search] -placeholder = "Cerca a les pàgines de configuració..." - [settings.security] description = "Actualitza la contrasenya per mantenir segur el compte." title = "Seguretat" @@ -10128,6 +10119,7 @@ last = "Última pàgina" maintainRatio = "Activa/desactiva mantenir la relació d'aspecte" next = "Pàgina següent" noSavedSigs = "No s'han trobat signatures desades" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Signatures Personals" previous = "Pàgina anterior" redo = "Refer" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "No s'ha pogut eliminar el fons" hint = "Carrega una imatge PNG o JPG de la teva signatura" label = "Carrega la imatge de la signatura" placeholder = "Selecciona el fitxer d'imatge" +previewAlt = "Current image signature" processing = "Processant la imatge..." removeBackground = "Elimina el fons blanc (fes-lo transparent)" @@ -10179,6 +10172,17 @@ saved = "Selecciona una signatura desada a dalt i fes clic a qualsevol lloc del text = "Després d'introduir el teu nom a dalt, fes clic en qualsevol lloc del PDF per col·locar la signatura." title = "Com afegir la signatura" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Mou la signatura" pause = "Pausa la col·locació" @@ -10625,6 +10629,23 @@ title = "Pujar al servidor" updateButton = "Actualitza al servidor" uploadButton = "Puja al servidor" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Torna enrere" pdfTools = "Eines PDF" placeholder = "Tria una eina per començar" premiumFeature = "Funció premium:" -searchTools = "Cerca eines" toolsHeader = "Eines" viewAllTools = "Mostra totes les eines" @@ -10822,6 +10842,9 @@ removal = "Eliminació" signing = "Signatura" verification = "Verificació" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "No s’han trobat eines" noTools = "No hi ha eines disponibles" @@ -10979,6 +11002,13 @@ approver = "Aprova la política" editor = "Editor" processor = "Processador" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Vols cancel·lar la invitació a {{email}}? No es podrà unir amb l'enllaç actual." cancelInviteTitle = "Cancel·la la invitació" @@ -10998,10 +11028,8 @@ title = "Encara no hi ha membres" addToTeam = "Afegeix a l'equip" guestCount = "{{count}} convidat" guests = "Convidats" -guestsDesc = "Col·laboradors externs, limitats al que has compartit. Només Editor." ledBy = "dirigit per {{owner}}" org = "Organització" -orgDesc = "Propietaris amb autoritat a tota l'organització i aprovació de polítiques" owners = "{{count}} propietari" team = "Equip {{name}}" teamMeta = "{{count}} persones" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Convidat per {{who}}" cancel = "Cancel·la" -count = "{{count}} pendents" -desc = "Persones convidades que encara no s'han unit. Ocupen una plaça fins que acceptin." expiresInDays_one = "Caduca d'aquí a {{count}} dia" expiresInDays_other = "Caduca d'aquí a {{count}} dies" expiresToday = "Caduca avui" title = "Invitacions pendents" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "S'ha produït un error en contactar amb el backend, o no hi tens accés. Torna-ho a provar." title = "No s'han pogut carregar els membres" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Cerca adjunts" title = "Adjunts" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Títol del marcador" bookmarkTitleRequired = "El títol del marcador és obligatori" closeSidebar = "Tanca la barra lateral de marcadors" collapseAll = "Replega tots els marcadors" +empty = "No bookmarks in this document" expandAll = "Desplega tots els marcadors" searchPlaceholder = "Cerca marcadors" +title = "Bookmarks" [viewer.comments] addComment = "Afegeix un comentari" @@ -11264,10 +11297,12 @@ locateAnnotation = "Localitza al document" moreActions = "Més accions" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Pàgina {{page}}" placingHint = "Feu clic a una pàgina per col·locar… (cancel·la)" removeCommentOnly = "Elimina només el comentari" saveReply = "Desa la resposta" +searchPlaceholder = "Search comments" title = "Comentaris" typeComment = "Comentari" typeInsertText = "Insereix text" @@ -11290,6 +11325,7 @@ unsavedDesc = "Tens canvis no desats" closeSidebar = "Tanca la barra lateral de capes" hideAll = "Amaga totes les capes" showAll = "Mostra totes les capes" +title = "Layers" [viewer.link] delete = "Suprimeix l'enllaç" @@ -11646,6 +11682,7 @@ exitRedaction = "Surt del mode de censura" exportAll = "Exporta el PDF" exportSelected = "Exporta les pàgines seleccionades" formFill = "Omple el formulari" +hideToolbar = "Hide toolbar" multiTool = "Eina múltiple" panMode = "Mode de desplaçament" print = "Imprimeix el PDF" @@ -11666,6 +11703,7 @@ selectAll = "Selecciona-ho tot" selectByNumber = "Selecciona per números de pàgina" selectLanguage = "Selecciona l’idioma" share = "Comparteix" +showToolbar = "Show toolbar" toggleAnnotations = "Mostra/oculta les anotacions" toggleAttachments = "Mostra/oculta els adjunts" toggleBookmarks = "Mostra/oculta els marcadors" diff --git a/frontend/editor/public/locales/cs-CZ/translation.toml b/frontend/editor/public/locales/cs-CZ/translation.toml index e4b31516a7..ead7dcab24 100644 --- a/frontend/editor/public/locales/cs-CZ/translation.toml +++ b/frontend/editor/public/locales/cs-CZ/translation.toml @@ -1,7 +1,6 @@ alphabet = "Abeceda" apply = "Použít" applyAndContinue = "Uložit a odejít" -areYouSure = "Opravdu chcete odejít?" back = "Zpět" black = "Černá" blue = "Modrá" @@ -106,6 +105,7 @@ undoSuccess = "Operace úspěšně vrácena" unknown = "Neznámý" unpin = "Odepnout soubor (nahradit po spuštění nástroje)" unsavedChanges = "Máte neuložené změny v PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Neuložené změny" unsupported = "Nepodporováno" username = "Uživatelské jméno" @@ -1879,6 +1879,9 @@ width = "Šířka" [app] description = "Bezplatná alternativa k Adobe Acrobat (10+ mil. stažení)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Convert to PDF/A-3b" convertToPdfA3bDescription = "Creates an archival PDF with embedded attachments" @@ -2933,12 +2936,16 @@ text = "Abyste tato oprávnění učinili nezměnitelnými, použijte nástroj P [chat.actions] copy = "Kopírovat zprávu" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Možnosti agenta Stirling" +assistant = "AI Assistant" clearChat = "Vymazat chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Tato operace využije vaše cloudové kredity" tooltip = "Vybrat barvu z obrazovky" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Zvolte barvu" [common] +actions = "Actions" back = "Zpět" cancel = "Zrušit" close = "Zavřít" +codeSample = "Code sample" collapse = "Sbalit" confirm = "Potvrdit" continue = "Pokračovat" copied = "Zkopírováno!" copy = "Kopírovat" done = "Hotovo" +enterValue = "Enter value" error = "Chyba" expand = "Rozbalit" loading = "Načítání..." next = "Další" +open = "Open" preview = "Náhled" previous = "Předchozí" refresh = "Obnovit" remaining = "Zbývá" retry = "Zkusit znovu" save = "Save" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Vymazat vybrané" @@ -3185,6 +3199,7 @@ title = "Metoda komprese" [compress.settings] desiredSize = "Požadovaná velikost souboru" desiredSizePlaceholder = "Zadejte velikost" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Komprese je snadný způsob, jak zmenšit velikost souboru. Zvolte Velikost souboru pro zadání cílové velikosti a my upravíme kvalitu za vás. Zvolte Kvalitu pro ruční nastavení síly komprese." @@ -3387,6 +3402,24 @@ pdfOptions = "Možnosti PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Nejprve vyberte zdrojový formát" settings = "Nastavení" single = "Jeden" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Hledat..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Upravit" @@ -3824,11 +3861,13 @@ shareSelected = "Sdílet vybrané" sharing = "Sdílení" showAll = "Zobrazit vše" showHistory = "Zobrazit historii" +sortBy = "Sort files" sortByDate = "Seřadit podle data" sortByName = "Seřadit podle názvu" sortBySize = "Seřadit podle velikosti" storage = "Úložiště" storageState = "Úložiště" +storageUsed = "Storage used" synced = "Synchronizováno" title = "Nahrát soubory PDF" toolChain = "Použité nástroje" @@ -3874,8 +3913,12 @@ addFiles = "Přidat soubory" addingFiles = "Přidávání souborů…" collapse = "Sbalit postranní panel" customizeGroups = "Přizpůsobit skupiny" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Otevřete soubory a začněte" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Rozbalit postranní panel" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive není nakonfigurován" @@ -3888,15 +3931,17 @@ openFromComputer = "Otevřít z počítače" openSettings = "Otevřít nastavení" other = "Ostatní" recent = "Nedávné" -search = "Hledat" -searchPlaceholder = "Hledat soubory..." viewAll = "Zobrazit všech {{count}} souborů" [fileSidebar.fileItem] closeViewer = "Zavřít prohlížeč" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Otevřít v prohlížeči" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Obnovit výchozí nastavení" subtitle = "Seskupte soubory do nadřazených kategorií. Přidejte do kategorie existující nebo nové štítky, přejmenujte ji nebo si vytvořte vlastní. Soubory, které nejsou v žádné z vašich kategorií, se zobrazí pod „Ostatní“." title = "Kategorie postranního panelu" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Přidat do pracovního prostoru" addToWorkspaceCount = "Přidat {{count}} do pracovního prostoru" @@ -3918,7 +3971,6 @@ backToMyFiles = "Zpět na Moje soubory" breadcrumbs = "Cesta ke složce" cancel = "Zrušit" classification = "Klasifikace" -clearSearch = "Vymazat hledání" clearSelection = "Zrušit výběr" closeDetails = "Zavřít podrobnosti" create = "Vytvořit" @@ -3950,6 +4002,7 @@ downloadAll = "Stáhnout vše" downloadVersion = "Stáhnout tuto verzi" dropOverlay = "Přetáhněte soubory pro nahrání" dropOverlaySub = "Soubory začínají v Místní. K jejich uspořádání do složky použijte 'Přesunout do' nebo 'Uložit do cloudu'." +duplicate = "Duplicate" file = "Soubor" fileInfo = "File info" fileMenu = "Akce souboru" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Změnit velikost stromu složek (šipky, Shift pro větší save = "Uložit" saveToServer = "Uložit na server" saveToServerDisabledHint = "Ukládání na server není na tomto serveru povoleno. Požádejte správce o povolení." -search = "Hledat" -searchPlaceholder = "Hledat v této složce a podsložkách" selectAll = "Vybrat vše" selectAllHint = "Kliknutím vyberete vše. Tip: podržte Ctrl (nebo Cmd) pro přidání jednotlivých souborů, Shift pro výběr rozsahu." selectedCount = "Vybráno: {{count}}" @@ -4044,6 +4095,10 @@ title = "Zatím žádné cloudové soubory" hint = "Soubory uložené bez nahrání zůstávají zde. Přetáhněte soubor pro přidání." title = "Žádné pouze místní soubory" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Soubory, které otevřete nebo upravíte, se zobrazí zde." title = "Zatím nic nebylo upraveno" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Nepodařilo se smazat složku." deleteFolderFailedDetail = "Nepodařilo se smazat složku: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Nepodařilo se upravit vzhled složky." folderAppearanceFailedDetail = "Nepodařilo se upravit vzhled složky: {{message}}" moveFilesFailed = "Nepodařilo se přesunout soubory." @@ -4113,6 +4170,11 @@ localHint = "Uloženo pouze v tomto prohlížeči" shared = "Sdílené" sharedHint = "Sdíleno s vámi přes odkaz" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Od nejstarších" modifiedDesc = "Od nejnovějších" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "V úložišti nejsou k dispozici žádné soubory. Nejpr open = "Otevřít" openFile = "Otevřít soubor" openFiles = "Otevřít soubory" +selectFile = "Select {{name}}" selectFromStorage = "Vybrat z úložiště" upload = "Nahrát" uploadFile = "Nahrát soubor" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Vše uloženo" +analyzingFields = "Analysing form fields..." extractCsvError = "Nepodařilo se extrahovat CSV" extractXlsxError = "Nepodařilo se extrahovat XLSX" +filled = "filled" flattenAfterFilling = "Zploštit po vyplnění" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "pov." requiredFieldsError = "Vyplňte prosím všechna povinná pole" rescanFields = "Znovu detekovat pole" @@ -4795,9 +4863,6 @@ title = "Obrázek na PDF" [imageToPdf] tags = "převod,img,jpg,obrázek,fotka" -[infoBanner] -dismiss = "Zavřít" - [invite] acceptError = "Nepodařilo se vytvořit účet" accountFor = "Vytváření účtu pro" @@ -4979,6 +5044,34 @@ uploadSuccess = "Nahrávání úspěšné!" uploadSuccessMessage = "Vaše obrázky byly přeneseny." validating = "Ověřuje se relace..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Naskenujte pro nahrání fotografií. Obrázky se automaticky převedou do PDF." descriptionNoConvert = "Naskenujte pro nahrání fotografií z mobilního zařízení." @@ -4997,8 +5090,13 @@ title = "Nahrát z mobilu" tags = "Multi nástroj,Více operací,UI,kliknutí a přetažení,přední strana,klientská strana,interaktivní,pohyb,smazat,přesunout,rozdělit" title = "PDF Multi nástroj" -[navbar] -search = "Hledat" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Ověření nebylo úspěšné. Toto okno můžete zavřít a zkusit to znovu." @@ -5127,6 +5225,7 @@ activeFiles = "Zobrazení Aktivní soubory ukazuje všechna PDF allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Zavřít" cropSettings = "Teď, když jsme vybrali soubor, který chceme oříznout, můžeme nakonfigurovat nástroj Oříznout a zvolit oblast, na kterou PDF ořízneme." +dialogLabel = "Onboarding" fileCheckbox = "Kliknutím na soubor ho vyberete ke zpracování. Pro dávkové operace můžete vybrat více souborů." fileReplacement = "Upravený soubor automaticky nahradí původní soubor na Pracovní ploše, takže jej můžete snadno poslat do dalších nástrojů." filesButton = "Tlačítko Soubory na panelu Rychlý přístup umožňuje nahrát PDF, na kterých chcete nástroje použít." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF na PDF/A" tags = "archiv,dlouhodobý,standard,převod,úložiště,uchování" title = "PDF na PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "tisk,standard,konverze,produkce,předtisk,archiv" title = "PDF na PDF/X" @@ -6045,6 +6151,7 @@ small = "500 kreditů" xsmall = "100 kreditů" [plan.availablePlans] +currency = "Billing currency" subtitle = "Vyberte plán, který vám vyhovuje" title = "Dostupné plány" @@ -6624,6 +6731,7 @@ reachedTitle = "Měsíční limit výdajů dosažen" title = "Nepodařilo se otevřít portál Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "z {{allowance}} bezplatných PDFs použito" capSuffix_other = "z {{allowance}} bezplatných PDFs použito" eyebrow = "Zkušební Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Odeslat dokument nebo oznámení e-mailem, když se spustí zásada." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Ukládat zpracované dokumenty do Nextcloud." @@ -7077,11 +7211,6 @@ title = "Oficiální SDKs" beta = "Beta" deprecated = "Zastaralé" -[portal.docs.search] -empty = "Žádná odpovídající dokumentace" -placeholder = "Hledat v dokumentaci" -results = "{{count}} výsledků" - [portal.docs.skills] eyebrow = "DOVEDNOSTI" lead = "Přibalené pojmenované schopnosti, které váš agent vyvolává jako jeden nástroj. Každá dovednost je deterministický řetězec operací s připojenými evals." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Tomuto filtru neodpovídají žádné dokumenty." rowActions = "Akce řádku" sensitiveLabel = "Citlivé" -sensitiveTitle = "Citlivé — je vyžadován přístup" [portal.documents.table.columns] action = "Pipeline / Akce" document = "Dokument" +labels = "Labels" product = "Produkt" status = "Stav" time = "Čas" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Naposledy viděno" region = "Region" status = "Stav" +target = "Target" version = "Verze" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "V tomto zobrazení došlo k neočekávané chybě. Zkuste to znov retry = "Zkusit znovu" title = "Na této stránce se něco pokazilo" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Zpět" body = "Stáhněte si desktopovou aplikaci nebo provozujte server self-hosted." @@ -7380,11 +7560,6 @@ cancel = "Zrušit" confirm = "Odvolat klíč" title = "Odvolat API klíč" -[portal.infrastructure.attestationLabel] -attested = "Ověřeno" -inScope = "V rozsahu" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtrovat auditní události podle kategorie" heading = "Auditní protokoly" @@ -7456,11 +7631,6 @@ info = "Info" success = "Úspěch" warning = "Varování" -[portal.infrastructure.certLabel] -certified = "Certifikováno" -inProgress = "Probíhá" -notStarted = "Nezahájeno" - [portal.infrastructure.createKey] cancel = "Zrušit" createKey = "Vytvořit klíč" @@ -7474,238 +7644,10 @@ subtitleCreated = "Zkopírujte si toto tajemství nyní — znovu se nezobrazí. title = "Vytvořit API klíč" titleCreated = "Klíč vytvořen" -[portal.infrastructure.deployLabel] -live = "Živě" -queued = "Ve frontě" -rolledBack = "Vráceno zpět" -rolling = "Nasazuje se" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Nasadil" -environment = "Prostředí" -product = "Produkt" -status = "Stav" -version = "Verze" -when = "Kdy" - -[portal.infrastructure.deployments.recent] -heading = "Nedávná nasazení" -subheading = "Nejnovější rollouty napříč produkty a prostředími." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instance" -latency = "Latence" -load = "Zatížení" -p99 = "P99" -region = "Region" -status = "Stav" -throughput = "Propustnost" -uptime = "Doba provozu" -version = "Verze" - -[portal.infrastructure.deployments.regions] -heading = "Regiony" -subheading = "Živý stav každého nasazeného regionu Stirling — latence, zatížení a verze rolloutů." - -[portal.infrastructure.deployments.regions.empty] -description = "Nasazené regiony se zde zobrazí, jakmile bude váš pracovní prostor zřízen." -title = "Žádné regiony nejsou nasazeny" - [portal.infrastructure.keyLabel] active = "Aktivní" revoked = "Odvoláno" -[portal.infrastructure.modelLabel] -active = "Aktivní" -degraded = "Zhoršené" -disabled = "Zakázáno" - -[portal.infrastructure.models] -heading = "Modely" -msValue = "{{value}} ms" -subheading = "Katalog modelů a směrování, které pohání zpracování dokumentů napříč vaším pracovním prostorem." - -[portal.infrastructure.models.byom] -description = "Zaregistrujte on-prem nebo self-hosted model a připněte jej k regionu pro zpracování vázané na datovou rezidenci." -title = "Přineste si vlastní model" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Spravované modely dostupné pro váš pracovní prostor, s živou latencí a náklady." -subEnterprise = "Spravované, vlastní a on-prem modely — s možností připnutí podle regionu." - -[portal.infrastructure.models.catalogue.empty] -description = "Modely v katalogu vašeho pracovního prostoru se zobrazí zde." -title = "Žádné modely nejsou dostupné" - -[portal.infrastructure.models.columns] -cost = "Náklady" -latency = "Latence" -load = "Zatížení" -model = "Model" -status = "Stav" -type = "Typ" -version = "Verze" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/volání" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktivní modely" -avgLatency = "Průměrná latence" -included = "Zahrnuto" -monthlySpend = "Měsíční výdaje za modely" - -[portal.infrastructure.models.routing] -empty = "Nejsou nakonfigurována žádná pravidla směrování." -heading = "Pravidla směrování" -sub = "Který model zpracovává jednotlivé operace. Výchozí se použije, když neodpovídá žádné užší pravidlo." -subLocked = "Směrujte operace na konkrétní modely — dostupné v placených plánech." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Upgradujte na Pro a určete, který model zpracovává jednotlivé operace a typy dokumentů." -title = "Směrování modelů je placená funkce" - -[portal.infrastructure.models.routingColumns] -default = "Výchozí" -docType = "Typ dokumentu" -modelForAria = "Model pro {{operation}}" -operation = "Operace" -routedTo = "Směrováno na" - -[portal.infrastructure.modelTypeLabel] -classification = "Klasifikace" -extraction = "Extrakce" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Zhoršené" -down = "Mimo provoz" -healthy = "V pořádku" - -[portal.infrastructure.security.access.byok] -description = "Poskytněte klíč z vlastního KMS. Stirling s ním šifruje, ale stále může číst." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Klíče nikdy neopustí váš KMS. Stirling uchovává pouze ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling spravuje šifrovací klíče. Nejjednodušší — na vaší straně žádné operace s klíči." -label = "Klíče držené Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Zásady přístupu k dokumentům" -subheading = "Řídí, kdo může dešifrovat zpracované dokumenty v klidu." - -[portal.infrastructure.security.attestations] -heading = "Atestace souladu" -noReport = "Není dostupná žádná zpráva" -subheading = "Auditní postoj podle jednotlivých rámců, se zprávami dostupnými u atestovaných kontrol." -viewReport = "Zobrazit zprávu →" - -[portal.infrastructure.security.compliance] -heading = "Soulad" -subheading = "Atestace a certifikace pokrývající platformu Stirling." - -[portal.infrastructure.security.empty] -description = "Konfigurace zabezpečení vašeho pracovního prostoru se zobrazí zde." -title = "Bezpečnostní postoj není dostupný" - -[portal.infrastructure.security.hyokBanner] -description = "S HYOK šifrovací klíče nikdy neopustí váš KMS. Stirling ukládá a zpracovává pouze ciphertext, který můžete kdykoli odvolat." -title = "Stirling nemůže dešifrovat vaše dokumenty" - -[portal.infrastructure.security.ipAllowlist] -empty = "Nejsou nakonfigurovány žádné IP rozsahy — všechny IP jsou povoleny." -heading = "IP allowlist" -sub = "API přístup je omezen na tyto CIDR rozsahy." -subLocked = "Omezte API přístup na známé IP rozsahy — dostupné v placených plánech." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Upgradujte na Pro a omezte API přístup na konkrétní sítě." -title = "IP allowlisting je placená funkce" - -[portal.infrastructure.security.ipColumns] -added = "Přidáno" -addedBy = "Přidal" -cidr = "CIDR" -label = "Štítek" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritmus" -heading = "Správa šifrovacích klíčů" -keyId = "Identifikátor klíče" -lastRotated = "Naposledy rotováno" -rotateKey = "Rotovat klíč" -rotationPolicy = "Zásady rotace" -subheading = "Správa klíčů, které šifrují dokumenty v klidu — kdo může dešifrovat a jak se klíče rotují." - -[portal.infrastructure.security.managedBanner] -description = "Správa klíčů bring-your-own-key (BYOK) a hold-your-own-key (HYOK) je dostupná v Enterprise. Upgradujte a poskytněte klíče z vlastního KMS." -title = "Klíče jsou ve vašem plánu spravovány Stirling" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asie a Pacifik" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · hranice dat GDPR" -label = "Evropská unie" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Spojené státy" - -[portal.infrastructure.security.residencyHeader] -heading = "Datová rezidence" -subheading = "Kde jsou dokumenty ukládány a zpracovávány." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} využito" - -[portal.infrastructure.storage.empty] -description = "Připojené úložiště a využití se zobrazí zde." -title = "Není nakonfigurováno žádné úložiště" - -[portal.infrastructure.storage.lifecycle] -active = "Aktivní" -activeRange = "0–{{value}} d" -archived = "Archivováno" -coldStorage = "cold storage" -deleted = "Smazáno" -never = "nikdy" -purged = "vymazáno" - -[portal.infrastructure.storage.providers] -connect = "Připojit" -connected = "Připojeno" -heading = "Připojení poskytovatelé" -subheading = "Kam se zapisují zpracované artefakty." - -[portal.infrastructure.storage.retention] -heading = "Uchovávání" -subheading = "Jak dlouho se artefakty uchovávají před odstraněním v rámci životního cyklu." -windowLabel = "Výchozí okno uchovávání" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} den" -days_other = "{{count}} dní" -never = "Nikdy nemazat" - -[portal.infrastructure.storage.totalUsage] -heading = "Celkové využití" -progressLabel = "Využité úložiště" -subheading = "Úložiště spotřebované napříč všemi připojenými poskytovateli." - [portal.infrastructure.tabs] apiKeys = "API klíče" audit = "Auditní protokoly" @@ -7715,11 +7657,9 @@ security = "Zabezpečení" storage = "Úložiště" [portal.integrations] -addAnother = "Přidat další" availableHeading = "Dostupné" comingSoonHeading = "Již brzy" connect = "Připojit" -connectedHeading = "Připojeno" connectionCount_one = "{{count}} připojení" connectionCount_other = "{{count}} připojení" customApi = "Vlastní API" @@ -7737,6 +7677,10 @@ security = "Zabezpečení" signing = "Podepisování" storage = "Úložiště" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Připojeno" @@ -7767,7 +7711,6 @@ integrations = "Integrace" pipelines = "Pipeline" policies = "Zásady" procurement = "Nákup" -settings = "Nastavení" sources = "Zdroje" usage = "Využití a fakturace" users = "Uživatelé" @@ -7784,7 +7727,7 @@ title = "Pipeline" newPipeline = "Nová pipeline" [portal.pipelines.builder] -addStep = "Přidat nástroj" +activate = "Activate" back = "Zpět na pipeline" cannotFollow = "Nelze přijmout {{produced}}" chooseAccount = "Vyberte účet" @@ -7792,28 +7735,38 @@ chooseDestination = "Vyberte cíl" chooseOperation = "Vyberte, co tento krok provede" chooseSource = "Vyberte zdroj" discard = "Zahodit změny" -enabled = "Povoleno" inputs = "Vstup" inputSource = "Vstupní zdroj" inputTrigger = "Spouštěč" keepEditing = "Pokračovat v úpravách" +moreActions = "More actions" needsConfiguring = "Vyžaduje nastavení" -needsUpload = "Vyžaduje nahraný soubor" -noSources = "Zatím nejsou připojeny žádné zdroje. Vytvořte jeden, abyste jej mohli použít jako vstup." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Vašemu hledání neodpovídají žádné nástroje." -pipelineSettings = "Nastavení pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Hledat nástroje" -selectToolBody = "Přidejte nástroj a vytvořte svou pipeline." -selectToolTitle = "Zatím žádné nástroje" sendToSystem = "Odeslat do jiného systému" stepsIncompatible = "Tyto kroky nelze spustit nad tím, co vytváří jejich předchozí krok: {{tools}}." stepsNeedSetup = "Tyto kroky je před uložením stále nutné nastavit: {{tools}}." -toolSettings = "Nastavení nástroje" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Nerozpoznaná operace, ponechána beze změny." unsavedBody = "Máte neuložené změny. Uložte je před odchodem, nebo je zahoďte?" unsavedTitle = "Neuložené změny" -uploadUnsupported = "Nahrané soubory zatím nejsou v pipeline podporovány, takže tyto kroky nelze uložit: {{tools}}." usesDefaults = "Spouští se s výchozím nastavením" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kombinuje každý soubor z předchozího kroku" @@ -7825,31 +7778,36 @@ undeclared-operation = "Nelze zkontrolovat, co tento krok přijímá" [portal.pipelines.composer] addTool = "Přidat nástroj" -cancel = "Zrušit" -chainEmpty = "Přidejte nástroj a začněte vytvářet svou pipeline." create = "Vytvořit pipeline" +createPaused = "Create paused" editingUnsupported = "Zobrazení těchto parametrů nástroje pro úpravy zatím není podporováno." -moveDown = "Přesunout dolů" -moveUp = "Přesunout nahoru" +editSource = "Edit source" name = "Název" namePlaceholder = "např. Kontrola redakcí" noToolSettings = "Tento nástroj nemá žádná konfigurovatelná nastavení." -operations_one = "Operace ({{count}})" -operations_other = "Operace ({{count}})" output = "Výstup" -removeStep = "Odebrat operaci" save = "Uložit změny" scheduleEvery = "Spustit každých" -sources = "Zdroje" -sourcesLoading = "Načítání zdrojů..." trigger = "Trigger" triggerManual = "Pouze ručně" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dní" hours = "hodin" minutes = "minut" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Smazat „{{name}}“? Tuto akci nelze vrátit zpět." cancel = "Zrušit" @@ -7867,6 +7825,37 @@ connectSource = "Připojit zdroj" description = "Vytvořte svou první pipeline: vyberte zdroje, nad kterými běží, zřetězte operace a zvolte, kam půjde výstup." title = "Zatím žádné pipeline" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktivní" paused = "Pozastaveno" @@ -7879,7 +7868,6 @@ completed_one = "Běh dokončen." completed_other = "Všech {{count}} běhů dokončeno." empty = "Není co spustit: zdroje neměly žádné dokumenty ke zpracování." failed = "Běh selhal: {{error}}" -historyCleared = "Historie vymazána. Příští spuštění znovu zpracuje vše, co je aktuálně ve zdrojích." inFlight = "Není co nového spustit: dokumenty se stále zpracovávají z dřívějšího spuštění." parked_one = "Není co spustit: {{count}} dokument dříve selhal a je odložen. Opravte příčinu a poté vymažte historii, abyste jej zkusili znovu." parked_other = "Není co spustit: {{count}} dokumentů dříve selhalo a jsou odloženy. Opravte příčinu a poté vymažte historii, abyste je zkusili znovu." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Zdroje" status = "Stav" steps = "Kroky" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Sledování složky" @@ -8209,6 +8198,24 @@ label = "Připojit k issue Jira" description = "Odešle zpracovaný dokument jako přílohu." label = "Odeslat dokument e-mailem (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Zapíše zpracovaný dokument do složky." label = "Nahrát do Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Vyčerpáno" subtitle = "Váš bezplatný trial běží do {{date}}. Karta není vyžadována." title = "Enterprise trial" -[portal.search] -ariaLabel = "Hledat" -placeholder = "Hledat ve Stirling — endpointy, pipeline, dokumentace…" - -[portal.search.empty] -noActionsDescription = "Rychlé akce se zde zobrazí, jakmile budou dostupné." -noActionsTitle = "Žádné rychlé akce" -noMatches = "Žádné shody pro „{{query}}“" -noMatchesDescription = "Zkuste jiné klíčové slovo nebo procházejte katalog." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Zrušit" confirm = "Odstranit" title = "Odstranit zdroj?" -[portal.sources.empty] -description = "Připojte složku (a brzy i cloudové úložiště), aby vaše zásady měly odkud dokumenty načítat." -title = "Zatím nejsou připojeny žádné zdroje" - [portal.sources.kpi] inUse = "Používá se" total = "Připojení" @@ -8785,6 +8778,7 @@ unused = "Nepoužito" documents = "Dokumenty" source = "Zdroj" status = "Stav" +type = "Type" usedBy = "Zásady" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Zásady a soukromí" [settings.preferences] title = "Předvolby" -[settings.search] -placeholder = "Hledat na stránkách nastavení..." - [settings.security] description = "Update your password to keep your account secure." title = "Security" @@ -10128,6 +10119,7 @@ last = "Poslední stránka" maintainRatio = "Přepnout zachování poměru stran" next = "Další stránka" noSavedSigs = "Nebyly nalezeny žádné uložené podpisy" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Osobní podpisy" previous = "Předchozí stránka" redo = "Znovu" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Background removal failed" hint = "Nahrajte PNG nebo JPG s vaším podpisem" label = "Nahrát obrázek podpisu" placeholder = "Vyberte obrazový soubor" +previewAlt = "Current image signature" processing = "Processing image..." removeBackground = "Remove white background (make transparent)" @@ -10179,6 +10172,17 @@ saved = "Vyberte výše uložený podpis a poté klikněte kamkoli do PDF pro je text = "Po zadání jména výše klikněte kamkoli do PDF pro umístění podpisu." title = "Jak přidat podpis" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Přesunout podpis" pause = "Pozastavit umisťování" @@ -10625,6 +10629,23 @@ title = "Nahrát na server" updateButton = "Aktualizovat na serveru" uploadButton = "Nahrát na server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Vrátit se" pdfTools = "Nástroje PDF" placeholder = "Vyberte nástroj pro začátek" premiumFeature = "Prémiová funkce:" -searchTools = "Hledat nástroje" toolsHeader = "Nástroje" viewAllTools = "Zobrazit všechny nástroje" @@ -10822,6 +10842,9 @@ removal = "Odstranění" signing = "Podepisování" verification = "Ověření" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nebyly nalezeny žádné nástroje" noTools = "Žádné dostupné nástroje" @@ -10979,6 +11002,13 @@ approver = "Schvaluje zásadu" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Zrušit pozvánku pro {{email}}? S aktuálním odkazem se nebudou moci připojit." cancelInviteTitle = "Zrušit pozvánku" @@ -10998,10 +11028,8 @@ title = "Zatím žádní členové" addToTeam = "Přidat do týmu" guestCount = "{{count}} host" guests = "Hosté" -guestsDesc = "Externí spolupracovníci, omezení na to, co jste sdíleli. Pouze Editor." ledBy = "vedeno uživatelem {{owner}}" org = "Organizace" -orgDesc = "Vlastníci s pravomocí v celé org. a schvalováním zásad" owners = "{{count}} vlastník" team = "Tým {{name}}" teamMeta = "{{count}} lidí" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Pozval(a) {{who}}" cancel = "Zrušit" -count = "{{count}} čeká" -desc = "Pozvaní lidé, kteří se ještě nepřipojili. Drží místo, dokud pozvánku nepřijmou." expiresInDays_one = "Vyprší za {{count}} den" expiresInDays_other = "Vyprší za {{count}} dní" expiresToday = "Vyprší dnes" title = "Čekající pozvánky" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Při spojení s backendem došlo k chybě, nebo nemáte přístup. Zkuste to znovu." title = "Členy se nepodařilo načíst" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Hledat přílohy" title = "Přílohy" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Název záložky" bookmarkTitleRequired = "Název záložky je povinný" closeSidebar = "Zavřít postranní panel záložek" collapseAll = "Sbalit všechny záložky" +empty = "No bookmarks in this document" expandAll = "Rozbalit všechny záložky" searchPlaceholder = "Hledat záložky" +title = "Bookmarks" [viewer.comments] addComment = "Přidat komentář" @@ -11264,10 +11297,12 @@ locateAnnotation = "Najít v dokumentu" moreActions = "Další akce" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Stránka {{page}}" placingHint = "Klikněte na stránku pro umístění… (zrušit)" removeCommentOnly = "Odebrat pouze komentář" saveReply = "Uložit odpověď" +searchPlaceholder = "Search comments" title = "Komentáře" typeComment = "Komentář" typeInsertText = "Vložit text" @@ -11290,6 +11325,7 @@ unsavedDesc = "Máte neuložené změny" closeSidebar = "Zavřít postranní panel vrstev" hideAll = "Skrýt všechny vrstvy" showAll = "Zobrazit všechny vrstvy" +title = "Layers" [viewer.link] delete = "Smazat odkaz" @@ -11646,6 +11682,7 @@ exitRedaction = "Opustit režim začernění" exportAll = "Exportovat PDF" exportSelected = "Exportovat vybrané stránky" formFill = "Vyplnit formulář" +hideToolbar = "Hide toolbar" multiTool = "Multinástroj" panMode = "Režim posunu" print = "Tisk PDF" @@ -11666,6 +11703,7 @@ selectAll = "Vybrat vše" selectByNumber = "Vybrat podle čísel stránek" selectLanguage = "Vyberte jazyk" share = "Sdílet" +showToolbar = "Show toolbar" toggleAnnotations = "Přepnout viditelnost anotací" toggleAttachments = "Přepnout přílohy" toggleBookmarks = "Přepnout záložky" diff --git a/frontend/editor/public/locales/da-DK/translation.toml b/frontend/editor/public/locales/da-DK/translation.toml index 057c6e8c16..90f17c7d0f 100644 --- a/frontend/editor/public/locales/da-DK/translation.toml +++ b/frontend/editor/public/locales/da-DK/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabet" apply = "Anvend" applyAndContinue = "Gem og forlad" -areYouSure = "Er du sikker på, at du vil forlade?" back = "Tilbage" black = "Sort" blue = "Blå" @@ -106,6 +105,7 @@ undoSuccess = "Handling fortrudt" unknown = "Ukendt" unpin = "Frigør fil (erstat efter værktøjskørsel)" unsavedChanges = "Du har ikke-gemte ændringer i din PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Ikke-gemte ændringer" unsupported = "Ikke understøttet" username = "Brugernavn" @@ -1879,6 +1879,9 @@ width = "Bredde" [app] description = "Det gratis alternativ til Adobe Acrobat (10M+ downloads)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Konverter til PDF/A-3b" convertToPdfA3bDescription = "Opretter en arkiv-PDF med indlejrede vedhæftninger" @@ -2933,12 +2936,16 @@ text = "For at gøre disse tilladelser uændrerlige skal du bruge værktøjet Ti [chat.actions] copy = "Kopiér besked" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling-agentindstillinger" +assistant = "AI Assistant" clearChat = "Ryd chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Denne handling vil bruge dine cloud-kreditter" tooltip = "Vælg farve fra skærmen" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Vælg farve" [common] +actions = "Actions" back = "Tilbage" cancel = "Annullér" close = "Luk" +codeSample = "Code sample" collapse = "Skjul" confirm = "Bekræft" continue = "Fortsæt" copied = "Kopieret!" copy = "Kopiér" done = "Færdig" +enterValue = "Enter value" error = "Fejl" expand = "Udvid" loading = "Indlæser..." next = "Næste" +open = "Open" preview = "Forhåndsvisning" previous = "Forrige" refresh = "Opdatér" remaining = "Resterende" retry = "Prøv igen" save = "Gem" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Ryd valgte" @@ -3185,6 +3199,7 @@ title = "Komprimeringsmetode" [compress.settings] desiredSize = "Ønsket filstørrelse" desiredSizePlaceholder = "Indtast størrelse" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Komprimering er en nem måde at reducere filstørrelsen på. Vælg filstørrelse for at angive en målstørrelse og lad os justere kvaliteten for dig. Vælg kvalitet for at indstille komprimeringsstyrken manuelt." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF-indstillinger" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Vælg først et kildeformat" settings = "Indstillinger" single = "Enkelt" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Søg..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Redigér" @@ -3824,11 +3861,13 @@ shareSelected = "Del valgte" sharing = "Deling" showAll = "Vis alle" showHistory = "Vis historik" +sortBy = "Sort files" sortByDate = "Sorter efter dato" sortByName = "Sorter efter navn" sortBySize = "Sorter efter størrelse" storage = "Lagerplads" storageState = "Lagring" +storageUsed = "Storage used" synced = "Synkroniseret" title = "Upload PDF-filer" toolChain = "Anvendte værktøjer" @@ -3874,8 +3913,12 @@ addFiles = "Tilføj filer" addingFiles = "Tilføjer filer…" collapse = "Sammenfold sidepanel" customizeGroups = "Tilpas grupper" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Åbn filer for at komme i gang" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Udvid sidepanel" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive er ikke konfigureret" @@ -3888,15 +3931,17 @@ openFromComputer = "Åbn fra computer" openSettings = "Åbn indstillinger" other = "Andet" recent = "Seneste" -search = "Søg" -searchPlaceholder = "Søg i filer..." viewAll = "Vis alle {{count}} filer" [fileSidebar.fileItem] closeViewer = "Luk fremviser" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Åbn i fremviser" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Nulstil til standarder" subtitle = "Gruppér dine filer i overordnede kategorier. Føj eksisterende eller nye etiketter til en kategori, omdøb den, eller opret din egen. Filer, der ikke er i nogen af dine kategorier, vises under “Andet”." title = "Sidebjælkekategorier" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Føj til arbejdsområde" addToWorkspaceCount = "Føj {{count}} til arbejdsområde" @@ -3918,7 +3971,6 @@ backToMyFiles = "Tilbage til Mine filer" breadcrumbs = "Mappesti" cancel = "Annuller" classification = "Klassifikation" -clearSearch = "Ryd søgning" clearSelection = "Ryd markering" closeDetails = "Luk detaljer" create = "Opret" @@ -3950,6 +4002,7 @@ downloadAll = "Download alle" downloadVersion = "Download denne version" dropOverlay = "Slip filer for at uploade" dropOverlaySub = "Filer starter i Lokal. Brug 'Flyt til' eller 'Gem til skyen' for at organisere dem i en mappe." +duplicate = "Duplicate" file = "Fil" fileInfo = "File info" fileMenu = "Filhandlinger" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Ændr størrelse på mappetræ (piletaster, Shift for størr save = "Gem" saveToServer = "Gem til server" saveToServerDisabledHint = "Lagring til serveren er ikke aktiveret på denne server. Bed din administrator om at aktivere det." -search = "Søg" -searchPlaceholder = "Søg i denne mappe og undermapper" selectAll = "Vælg alle" selectAllHint = "Klik for at vælge alle. Tip: hold Ctrl (eller Cmd) for at tilføje filer én ad gangen, Shift for at vælge et område." selectedCount = "{{count}} valgt" @@ -4044,6 +4095,10 @@ title = "Ingen skyfiler endnu" hint = "Filer gemt uden upload bliver her. Slip en fil for at tilføje en." title = "Ingen filer kun lokale" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Filer, du åbner eller redigerer, vil blive vist her." title = "Intet ændret endnu" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Kunne ikke slette mappen." deleteFolderFailedDetail = "Kunne ikke slette mappen: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Kunne ikke opdatere mappens udseende." folderAppearanceFailedDetail = "Kunne ikke opdatere mappens udseende: {{message}}" moveFilesFailed = "Kunne ikke flytte filer." @@ -4113,6 +4170,11 @@ localHint = "Kun gemt i denne browser" shared = "Delt" sharedHint = "Delt med dig via link" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Ældst først" modifiedDesc = "Nyest først" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Ingen filer tilgængelige i lageret. Åbn nogle filer f open = "Åbn" openFile = "Åbn fil" openFiles = "Åbn filer" +selectFile = "Select {{name}}" selectFromStorage = "Vælg fra lager" upload = "Upload" uploadFile = "Upload fil" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Alt gemt" +analyzingFields = "Analysing form fields..." extractCsvError = "Kunne ikke udtrække CSV" extractXlsxError = "Kunne ikke udtrække XLSX" +filled = "filled" flattenAfterFilling = "Fladgør efter udfyldning" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obl" requiredFieldsError = "Udfyld alle obligatoriske felter" rescanFields = "Scan felter igen" @@ -4795,9 +4863,6 @@ title = "Billede til PDF" [imageToPdf] tags = "konvertering,img,jpg,billede,foto" -[infoBanner] -dismiss = "Afvis" - [invite] acceptError = "Kunne ikke oprette konto" accountFor = "Opretter konto for" @@ -4979,6 +5044,34 @@ uploadSuccess = "Upload gennemført!" uploadSuccessMessage = "Dine billeder er overført." validating = "Validerer session..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Scan for at uploade fotos. Billeder konverteres automatisk til PDF." descriptionNoConvert = "Scan for at uploade fotos fra din mobilenhed." @@ -4997,8 +5090,13 @@ title = "Upload fra mobil" tags = "Multi Værktøj,Multi operation,UI,klik træk,front end,klient side,interaktiv,interagerbar,flyt" title = "PDF Multi Værktøj" -[navbar] -search = "Søg" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Godkendelsen var ikke vellykket. Du kan lukke dette vindue og prøve igen." @@ -5127,6 +5225,7 @@ activeFiles = "Visningen Aktive filer viser alle PDF'er, du har allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Luk" cropSettings = "Nu hvor vi har valgt den fil, vi vil beskære, kan vi konfigurere Beskær-værktøjet til at vælge det område, vi vil beskære PDF'en til." +dialogLabel = "Onboarding" fileCheckbox = "Klik på en af filerne for at vælge den til behandling. Du kan vælge flere filer til batchhandlinger." fileReplacement = "Den ændrede fil erstatter automatisk den originale fil i Arbejdsbordet, så du nemt kan køre den gennem flere værktøjer." filesButton = "Knappen Filer på værktøjslinjen til hurtig adgang lader dig uploade PDF'er til brug i værktøjerne." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF Til PDF/A" tags = "arkiv,langtids,standard,konvertering,opbevaring,bevaring" title = "PDF Til PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "udskrivning,standard,konvertering,produktion,førtryk,arkiv" title = "PDF til PDF/X" @@ -6045,6 +6151,7 @@ small = "500 kreditter" xsmall = "100 kreditter" [plan.availablePlans] +currency = "Billing currency" subtitle = "Vælg den plan, der passer til dine behov" title = "Tilgængelige planer" @@ -6624,6 +6731,7 @@ reachedTitle = "Månedlig forbrugsgrænse nået" title = "Kunne ikke åbne Stripe-portalen" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "af {{allowance}} gratis PDFs brugt" capSuffix_other = "af {{allowance}} gratis PDFs brugt" eyebrow = "Processor-prøveperiode" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Send et dokument eller en notifikation via email, når en politik kører." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Arkivér behandlede dokumenter i Nextcloud." @@ -7077,11 +7211,6 @@ title = "Officielle SDKs" beta = "Beta" deprecated = "Forældet" -[portal.docs.search] -empty = "Ingen matchende docs" -placeholder = "Søg i docs" -results = "{{count}} resultater" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Medfølgende, navngivne kapabiliteter, som din agent kalder som ét enkelt værktøj. Hver skill er en deterministisk operationskæde med evals tilknyttet." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Ingen dokumenter matcher dette filter." rowActions = "Rækkehandlinger" sensitiveLabel = "Følsom" -sensitiveTitle = "Følsom — adgang påkrævet" [portal.documents.table.columns] action = "Pipeline / handling" document = "Dokument" +labels = "Labels" product = "Produkt" status = "Status" time = "Tidspunkt" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Sidst set" region = "Region" status = "Status" +target = "Target" version = "Version" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Denne visning ramte en uventet fejl. Prøv igen, eller vælg en a retry = "Prøv igen" title = "Noget gik galt på denne side" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Tilbage" body = "Download desktop-appen, eller self-host serveren." @@ -7380,11 +7560,6 @@ cancel = "Annuller" confirm = "Tilbagekald nøgle" title = "Tilbagekald API-nøgle" -[portal.infrastructure.attestationLabel] -attested = "Attesteret" -inScope = "Inden for scope" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtrer revisionshændelser efter kategori" heading = "Revisionslogge" @@ -7456,11 +7631,6 @@ info = "Info" success = "Succes" warning = "Advarsel" -[portal.infrastructure.certLabel] -certified = "Certificeret" -inProgress = "I gang" -notStarted = "Ikke startet" - [portal.infrastructure.createKey] cancel = "Annuller" createKey = "Opret nøgle" @@ -7474,238 +7644,10 @@ subtitleCreated = "Kopiér denne hemmelighed nu — den vises ikke igen." title = "Opret API-nøgle" titleCreated = "Nøgle oprettet" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "I kø" -rolledBack = "Rullet tilbage" -rolling = "Udrulles" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Udrullet af" -environment = "Miljø" -product = "Produkt" -status = "Status" -version = "Version" -when = "Hvornår" - -[portal.infrastructure.deployments.recent] -heading = "Seneste udrulninger" -subheading = "De seneste udrulninger på tværs af produkter og miljøer." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instanser" -latency = "Latens" -load = "Belastning" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Gennemløb" -uptime = "Oppetid" -version = "Version" - -[portal.infrastructure.deployments.regions] -heading = "Regioner" -subheading = "Live tilstand for hver udrullet Stirling-region — latens, belastning og udrulningsversion." - -[portal.infrastructure.deployments.regions.empty] -description = "Udrullede regioner vises her, når dit arbejdsområde er provisioneret." -title = "Ingen regioner udrullet" - [portal.infrastructure.keyLabel] active = "Aktiv" revoked = "Tilbagekaldt" -[portal.infrastructure.modelLabel] -active = "Aktiv" -degraded = "Forringet" -disabled = "Deaktiveret" - -[portal.infrastructure.models] -heading = "Modeller" -msValue = "{{value}} ms" -subheading = "Modelkataloget og routingen, der driver dokumentbehandling på tværs af dit arbejdsområde." - -[portal.infrastructure.models.byom] -description = "Registrer en on-prem- eller selvhostet model, og fastgør den til en region til behandling bundet af dataplacering." -title = "Brug din egen model" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Administrerede modeller tilgængelige for dit arbejdsområde med live latens og omkostning." -subEnterprise = "Administrerede, bring-your-own og on-prem-modeller — med fastgørelse pr. region tilgængelig." - -[portal.infrastructure.models.catalogue.empty] -description = "Modeller i dit arbejdsområdes katalog vises her." -title = "Ingen modeller tilgængelige" - -[portal.infrastructure.models.columns] -cost = "Omkostning" -latency = "Latens" -load = "Belastning" -model = "Model" -status = "Status" -type = "Type" -version = "Version" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/kald" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktive modeller" -avgLatency = "Gns. latens" -included = "Inkluderet" -monthlySpend = "Månedligt modelforbrug" - -[portal.infrastructure.models.routing] -empty = "Ingen routingregler konfigureret." -heading = "Routingregler" -sub = "Hvilken model håndterer hver operation. Standarden anvendes, når ingen snævrere regel matcher." -subLocked = "Route operationer til specifikke modeller — tilgængeligt på betalte planer." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Opgrader til Pro for at styre, hvilken model der håndterer hver operation og dokumenttype." -title = "Model-routing er en betalt funktion" - -[portal.infrastructure.models.routingColumns] -default = "Standard" -docType = "Dokumenttype" -modelForAria = "Model for {{operation}}" -operation = "Operation" -routedTo = "Routet til" - -[portal.infrastructure.modelTypeLabel] -classification = "Klassifikation" -extraction = "Udtrækning" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Forringet" -down = "Nede" -healthy = "Sund" - -[portal.infrastructure.security.access.byok] -description = "Lever en nøgle fra dit eget KMS. Stirling krypterer med den, men kan stadig læse." -label = "Brug din egen nøgle (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Nøgler forlader aldrig dit KMS. Stirling har kun ciphertext." -label = "Behold din egen nøgle (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling administrerer krypteringsnøgler. Enklest — nul nøgleoperationer fra din side." -label = "Stirling-holdte nøgler" - -[portal.infrastructure.security.accessPolicy] -heading = "Politik for dokumentadgang" -subheading = "Styrer, hvem der kan dekryptere behandlede dokumenter i hvile." - -[portal.infrastructure.security.attestations] -heading = "Complianceattestationer" -noReport = "Ingen rapport tilgængelig" -subheading = "Revisionsstatus framework for framework med rapporter tilgængelige for attesterede kontroller." -viewReport = "Se rapport →" - -[portal.infrastructure.security.compliance] -heading = "Compliance" -subheading = "Attestationer og certificeringer, der dækker Stirling-platformen." - -[portal.infrastructure.security.empty] -description = "Dit arbejdsområdes sikkerhedskonfiguration vises her." -title = "Sikkerhedsstatus utilgængelig" - -[portal.infrastructure.security.hyokBanner] -description = "Med HYOK forlader krypteringsnøgler aldrig dit KMS. Stirling gemmer og behandler kun ciphertext, som du kan tilbagekalde når som helst." -title = "Stirling kan ikke dekryptere dine dokumenter" - -[portal.infrastructure.security.ipAllowlist] -empty = "Ingen IP-intervaller konfigureret — alle IP'er tilladt." -heading = "IP allowlist" -sub = "API-adgang er begrænset til disse CIDR-intervaller." -subLocked = "Begræns API-adgang til kendte IP-intervaller — tilgængeligt på betalte planer." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Opgrader til Pro for at begrænse API-adgang til specifikke netværk." -title = "IP allowlisting er en betalt funktion" - -[portal.infrastructure.security.ipColumns] -added = "Tilføjet" -addedBy = "Tilføjet af" -cidr = "CIDR" -label = "Etiket" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritme" -heading = "Administration af krypteringsnøgler" -keyId = "Nøgleidentifikator" -lastRotated = "Sidst roteret" -rotateKey = "Rotér nøgle" -rotationPolicy = "Rotationspolitik" -subheading = "Forvaltning af nøglerne, der krypterer dokumenter i hvile — hvem der kan dekryptere, og hvordan nøgler roteres." - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key (BYOK) og hold-your-own-key (HYOK) forvaltning er tilgængelige på Enterprise. Opgrader for at levere nøgler fra dit eget KMS." -title = "Nøgler administreres af Stirling på din plan" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asien-Stillehavsområdet" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR-datagrænse" -label = "Den Europæiske Union" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "USA" - -[portal.infrastructure.security.residencyHeader] -heading = "Dataplacering" -subheading = "Hvor dokumenter gemmes og behandles." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} brugt" - -[portal.infrastructure.storage.empty] -description = "Forbundet lager og forbrug vises her." -title = "Intet lager konfigureret" - -[portal.infrastructure.storage.lifecycle] -active = "Aktiv" -activeRange = "0–{{value}}d" -archived = "Arkiveret" -coldStorage = "kold lagring" -deleted = "Slettet" -never = "aldrig" -purged = "renset" - -[portal.infrastructure.storage.providers] -connect = "Forbind" -connected = "Forbundet" -heading = "Forbundne udbydere" -subheading = "Hvor behandlede artefakter skrives." - -[portal.infrastructure.storage.retention] -heading = "Opbevaring" -subheading = "Hvor længe artefakter bevares før livscyklussletning." -windowLabel = "Standard opbevaringsvindue" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dag" -days_other = "{{count}} dage" -never = "Slet aldrig" - -[portal.infrastructure.storage.totalUsage] -heading = "Samlet forbrug" -progressLabel = "Lager brugt" -subheading = "Lager forbrugt på tværs af alle forbundne udbydere." - [portal.infrastructure.tabs] apiKeys = "API-nøgler" audit = "Revisionslogge" @@ -7715,11 +7657,9 @@ security = "Sikkerhed" storage = "Lager" [portal.integrations] -addAnother = "Tilføj en mere" availableHeading = "Tilgængelige" comingSoonHeading = "Kommer snart" connect = "Forbind" -connectedHeading = "Forbundet" connectionCount_one = "{{count}} forbindelse" connectionCount_other = "{{count}} forbindelser" customApi = "Brugerdefineret API" @@ -7737,6 +7677,10 @@ security = "Sikkerhed" signing = "Signering" storage = "Lager" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Forbundet" @@ -7767,7 +7711,6 @@ integrations = "Integrationer" pipelines = "Pipelines" policies = "Politikker" procurement = "Indkøb" -settings = "Indstillinger" sources = "Kilder" usage = "Forbrug og fakturering" users = "Brugere" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Ny pipeline" [portal.pipelines.builder] -addStep = "Tilføj værktøj" +activate = "Activate" back = "Tilbage til pipelines" cannotFollow = "Kan ikke tage {{produced}}" chooseAccount = "Vælg en konto" @@ -7792,28 +7735,38 @@ chooseDestination = "Vælg en destination" chooseOperation = "Vælg, hvad dette trin gør" chooseSource = "Vælg en kilde" discard = "Kassér ændringer" -enabled = "Aktiveret" inputs = "Input" inputSource = "Inputkilde" inputTrigger = "Trigger" keepEditing = "Fortsæt redigering" +moreActions = "More actions" needsConfiguring = "Skal opsættes" -needsUpload = "Kræver en uploadet fil" -noSources = "Ingen kilder er forbundet endnu. Opret en for at bruge den som input." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Ingen værktøjer matcher din søgning." -pipelineSettings = "Pipelineindstillinger" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Søg i værktøjer" -selectToolBody = "Tilføj et værktøj for at bygge din pipeline." -selectToolTitle = "Ingen værktøjer endnu" sendToSystem = "Send til et andet system" stepsIncompatible = "Disse trin kan ikke køre på det, deres forrige trin producerer: {{tools}}." stepsNeedSetup = "Disse trin skal stadig opsættes før lagring: {{tools}}." -toolSettings = "Værktøjsindstillinger" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Ukendt operation, bevaret som den er." unsavedBody = "Du har ændringer, der ikke er gemt. Gem dem, før du forlader siden, eller kassér dem?" unsavedTitle = "Ikke-gemte ændringer" -uploadUnsupported = "Uploadede filer understøttes endnu ikke i pipelines, så disse trin kan ikke gemmes: {{tools}}." usesDefaults = "Kører med standardindstillinger" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kombinerer alle filer fra det forrige trin" @@ -7825,31 +7778,36 @@ undeclared-operation = "Kan ikke kontrollere, hvad dette trin accepterer" [portal.pipelines.composer] addTool = "Tilføj værktøj" -cancel = "Annuller" -chainEmpty = "Tilføj et værktøj for at begynde at bygge din pipeline." create = "Opret pipeline" +createPaused = "Create paused" editingUnsupported = "Visning af disse værktøjsparametre til redigering understøttes endnu ikke." -moveDown = "Flyt ned" -moveUp = "Flyt op" +editSource = "Edit source" name = "Navn" namePlaceholder = "f.eks. Redaction sweep" noToolSettings = "Dette værktøj har ingen konfigurerbare indstillinger." -operations_one = "Operation ({{count}})" -operations_other = "Operationer ({{count}})" output = "Output" -removeStep = "Fjern operation" save = "Gem ændringer" scheduleEvery = "Kør hver" -sources = "Kilder" -sourcesLoading = "Indlæser kilder..." trigger = "Trigger" triggerManual = "Kun manuel" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dage" hours = "timer" minutes = "minutter" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Slet \"{{name}}\"? Dette kan ikke fortrydes." cancel = "Annuller" @@ -7867,6 +7825,37 @@ connectSource = "Forbind en kilde" description = "Opret din første pipeline: vælg de kilder, den kører over, kæd operationerne sammen, og vælg, hvor output skal placeres." title = "Ingen pipelines endnu" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktive" paused = "Sat på pause" @@ -7879,7 +7868,6 @@ completed_one = "Kørsel fuldført." completed_other = "Alle {{count}} kørsler fuldført." empty = "Intet at køre: kilderne havde ingen dokumenter at behandle." failed = "Kørsel mislykkedes: {{error}}" -historyCleared = "Historik ryddet. Næste kørsel genbehandler alt, der aktuelt er i kilderne." inFlight = "Intet nyt at køre: dokumenter behandles stadig fra en tidligere kørsel." parked_one = "Intet at køre: {{count}} dokument fejlede tidligere og er parkeret. Ret årsagen, og ryd derefter historikken for at prøve igen." parked_other = "Intet at køre: {{count}} dokumenter fejlede tidligere og er parkeret. Ret årsagen, og ryd derefter historikken for at prøve igen." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Kilder" status = "Status" steps = "Trin" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Mappeovervågning" @@ -8209,6 +8198,24 @@ label = "Vedhæft til en Jira-sag" description = "Sender det behandlede dokument som en vedhæftning." label = "Send dokumentet via email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Skriver det behandlede dokument til en mappe." label = "Upload til Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Maksimum nået" subtitle = "Din gratis prøveperiode løber til {{date}}. Intet kort kræves." title = "Enterprise-prøveperiode" -[portal.search] -ariaLabel = "Søg" -placeholder = "Søg i Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Hurtighandlinger vises her, når de er tilgængelige." -noActionsTitle = "Ingen hurtighandlinger" -noMatches = "Ingen matches for \"{{query}}\"" -noMatchesDescription = "Prøv et andet søgeord, eller gennemse kataloget." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Annuller" confirm = "Slet" title = "Slet kilde?" -[portal.sources.empty] -description = "Forbind en mappe (og snart cloud storage), så dine politikker har et sted at hente dokumenter fra." -title = "Ingen kilder forbundet endnu" - [portal.sources.kpi] inUse = "I brug" total = "Forbindelser" @@ -8785,6 +8778,7 @@ unused = "Ubrugt" documents = "Dokumenter" source = "Kilde" status = "Status" +type = "Type" usedBy = "Politikker" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Politikker & Privatliv" [settings.preferences] title = "Præferencer" -[settings.search] -placeholder = "Søg i indstillingssider..." - [settings.security] description = "Opdater din adgangskode for at holde din konto sikker." title = "Sikkerhed" @@ -10128,6 +10119,7 @@ last = "Sidste side" maintainRatio = "Skift bevarelse af billedforhold" next = "Næste side" noSavedSigs = "Ingen Gemte Signaturer Fundet" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Personlige Signaturer" previous = "Forrige side" redo = "Gentag" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Fjernelse af baggrund mislykkedes" hint = "Upload et PNG- eller JPG-billede af din underskrift" label = "Upload billede af underskrift" placeholder = "Vælg billedfil" +previewAlt = "Current image signature" processing = "Behandler billede..." removeBackground = "Fjern hvid baggrund (gør gennemsigtig)" @@ -10179,6 +10172,17 @@ saved = "Vælg en gemt signatur ovenfor, og klik derefter hvor som helst på PDF text = "Når du har indtastet dit navn ovenfor, klik hvor som helst på PDF'en for at placere din underskrift." title = "Sådan tilføjer du underskrift" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Flyt underskrift" pause = "Sæt placering på pause" @@ -10625,6 +10629,23 @@ title = "Upload til server" updateButton = "Opdater på server" uploadButton = "Upload til server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Gå tilbage" pdfTools = "PDF-værktøjer" placeholder = "Vælg et værktøj for at komme i gang" premiumFeature = "Premium-funktion:" -searchTools = "Søg i værktøjer" toolsHeader = "Værktøjer" viewAllTools = "Vis alle værktøjer" @@ -10822,6 +10842,9 @@ removal = "Fjernelse" signing = "Signering" verification = "Verifikation" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Ingen værktøjer fundet" noTools = "Ingen værktøjer tilgængelige" @@ -10979,6 +11002,13 @@ approver = "Godkender politik" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Annuller invitationen til {{email}}? De vil ikke kunne deltage med det aktuelle link." cancelInviteTitle = "Annuller invitation" @@ -10998,10 +11028,8 @@ title = "Ingen medlemmer endnu" addToTeam = "Føj til team" guestCount = "{{count}} gæst" guests = "Gæster" -guestsDesc = "Eksterne samarbejdspartnere, begrænset til det, du har delt. Kun Editor." ledBy = "ledet af {{owner}}" org = "Organisation" -orgDesc = "Ejere med organisationsdækkende autoritet og politikgodkendelse" owners = "{{count}} ejer" team = "{{name}}-team" teamMeta = "{{count}} personer" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Inviteret af {{who}}" cancel = "Annuller" -count = "{{count}} afventende" -desc = "Inviterede personer, som ikke har tilmeldt sig endnu. De optager en plads, indtil de accepterer." expiresInDays_one = "Udløber om {{count}} dag" expiresInDays_other = "Udløber om {{count}} dage" expiresToday = "Udløber i dag" title = "Afventende invitationer" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Noget gik galt ved kontakt til backend, eller du har ikke adgang. Prøv igen." title = "Kunne ikke indlæse medlemmer" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Søg i vedhæftninger" title = "Vedhæftninger" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Bogmærketitel" bookmarkTitleRequired = "Bogmærketitel er påkrævet" closeSidebar = "Luk sidepanelet for bogmærker" collapseAll = "Skjul alle bogmærker" +empty = "No bookmarks in this document" expandAll = "Udvid alle bogmærker" searchPlaceholder = "Søg i bogmærker" +title = "Bookmarks" [viewer.comments] addComment = "Tilføj kommentar" @@ -11264,10 +11297,12 @@ locateAnnotation = "Find i dokument" moreActions = "Flere handlinger" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Side {{page}}" placingHint = "Klik på en side for at placere… (annuller)" removeCommentOnly = "Fjern kun kommentar" saveReply = "Gem svar" +searchPlaceholder = "Search comments" title = "Kommentarer" typeComment = "Kommentar" typeInsertText = "Indsæt tekst" @@ -11290,6 +11325,7 @@ unsavedDesc = "Du har ikke-gemte ændringer" closeSidebar = "Luk sidepanelet for lag" hideAll = "Skjul alle lag" showAll = "Vis alle lag" +title = "Layers" [viewer.link] delete = "Slet link" @@ -11646,6 +11682,7 @@ exitRedaction = "Afslut maskeringstilstand" exportAll = "Eksporter PDF" exportSelected = "Eksporter valgte sider" formFill = "Udfyld formular" +hideToolbar = "Hide toolbar" multiTool = "Multiværktøj" panMode = "Panoreringstilstand" print = "Udskriv PDF" @@ -11666,6 +11703,7 @@ selectAll = "Vælg alle" selectByNumber = "Vælg efter sidetal" selectLanguage = "Vælg sprog" share = "Del" +showToolbar = "Show toolbar" toggleAnnotations = "Slå anmærkninger til/fra" toggleAttachments = "Slå vedhæftede filer til/fra" toggleBookmarks = "Slå bogmærker til/fra" diff --git a/frontend/editor/public/locales/de-DE/translation.toml b/frontend/editor/public/locales/de-DE/translation.toml index 75fcb2376a..d4de81d3ee 100644 --- a/frontend/editor/public/locales/de-DE/translation.toml +++ b/frontend/editor/public/locales/de-DE/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alphabet" apply = "Anwenden" applyAndContinue = "Anwenden & Fortfahren" -areYouSure = "Möchten Sie die Seite wirklich verlassen?" back = "Zurück" black = "Schwarz" blue = "Blau" @@ -106,6 +105,7 @@ undoSuccess = "Vorgang erfolgreich rückgängig gemacht" unknown = "Unbekannt" unpin = "Datei lösen (nach Tool-Ausführung ersetzen)" unsavedChanges = "Sie haben ungespeicherte Änderungen an Ihrer PDF. Was möchten Sie tun?" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Ungespeicherte Änderungen" unsupported = "Nicht unterstützt" username = "Benutzername" @@ -1879,6 +1879,9 @@ width = "Width" [app] description = "Die kostenlose Adobe Acrobat Alternative (über 10 Millionen Downloads)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "In PDF/A-3b konvertieren" convertToPdfA3bDescription = "Erstellt ein Archiv-PDF mit eingebetteten Anhängen" @@ -2933,12 +2936,16 @@ text = "Um diese Berechtigungen unveränderlich zu machen, verwenden Sie das Pas [chat.actions] copy = "Nachricht kopieren" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling-Agent-Optionen" +assistant = "AI Assistant" clearChat = "Chat löschen" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Dieser Vorgang verwendet Ihre Cloud-Credits" tooltip = "Farbe vom Bildschirm aufnehmen" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Farbe wählen" [common] +actions = "Actions" back = "Zurück" cancel = "Abbrechen" close = "Schließen" +codeSample = "Code sample" collapse = "Einklappen" confirm = "Bestätigen" continue = "Weiter" copied = "Kopiert!" copy = "Kopieren" done = "Fertig" +enterValue = "Enter value" error = "Fehler" expand = "Ausklappen" loading = "Laden..." next = "Weiter" +open = "Open" preview = "Vorschau" previous = "Zurück" refresh = "Aktualisieren" remaining = "Verbleibend" retry = "Wiederholen" save = "Save" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Auswahl löschen" @@ -3185,6 +3199,7 @@ title = "Kompressions-Methode" [compress.settings] desiredSize = "Gewünschte Dateigröße" desiredSizePlaceholder = "Größe eingeben" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Komprimierung ist eine einfache Möglichkeit, Ihre Dateigröße zu reduzieren. Wählen Sie Dateigröße, um eine Zielgröße einzugeben und wir passen die Qualität für Sie an. Wählen Sie Qualität, um die Komprimierungsstärke manuell festzulegen." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF-Optionen" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Wählen Sie zuerst ein Quellformat aus" settings = "Einstellungen" single = "Einzeln" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Suchen..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Bearbeiten" @@ -3824,11 +3861,13 @@ shareSelected = "Auswahl freigeben" sharing = "Freigabe" showAll = "Alle anzeigen" showHistory = "Verlauf anzeigen" +sortBy = "Sort files" sortByDate = "Nach Datum sortieren" sortByName = "Nach Name sortieren" sortBySize = "Nach Größe sortieren" storage = "Speicher" storageState = "Speicher" +storageUsed = "Storage used" synced = "Synchronisiert" title = "PDF-Dateien hochladen" toolChain = "Angewendete Werkzeuge" @@ -3874,8 +3913,12 @@ addFiles = "Dateien hinzufügen" addingFiles = "Dateien werden hinzugefügt…" collapse = "Seitenleiste einklappen" customizeGroups = "Gruppen anpassen" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Öffnen Sie Dateien, um zu beginnen" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Seitenleiste ausklappen" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive ist nicht konfiguriert" @@ -3888,15 +3931,17 @@ openFromComputer = "Vom Computer öffnen" openSettings = "Einstellungen öffnen" other = "Andere" recent = "Zuletzt verwendet" -search = "Suchen" -searchPlaceholder = "Dateien suchen..." viewAll = "Alle {{count}} Dateien anzeigen" [fileSidebar.fileItem] closeViewer = "Viewer schließen" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Im Viewer öffnen" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Auf Standardwerte zurücksetzen" subtitle = "Gruppieren Sie Ihre Dateien in übergeordnete Kategorien. Fügen Sie vorhandene oder neue Labels zu einer Kategorie hinzu, benennen Sie sie um oder erstellen Sie eine eigene. Dateien, die in keiner Ihrer Kategorien sind, erscheinen unter „Andere“." title = "Seitenleisten-Kategorien" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Zum Arbeitsbereich hinzufügen" addToWorkspaceCount = "{{count}} zum Arbeitsbereich hinzufügen" @@ -3918,7 +3971,6 @@ backToMyFiles = "Zurück zu Meine Dateien" breadcrumbs = "Ordnerpfad" cancel = "Abbrechen" classification = "Klassifizierung" -clearSearch = "Suche löschen" clearSelection = "Auswahl aufheben" closeDetails = "Details schließen" create = "Erstellen" @@ -3950,6 +4002,7 @@ downloadAll = "Alle herunterladen" downloadVersion = "Diese Version herunterladen" dropOverlay = "Dateien zum Hochladen hier ablegen" dropOverlaySub = "Dateien starten in Lokal. Verwenden Sie 'Verschieben nach' oder 'In die Cloud speichern', um sie in einem Ordner zu organisieren." +duplicate = "Duplicate" file = "Datei" fileInfo = "File info" fileMenu = "Dateiaktionen" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Ordnerbaum skalieren (Pfeiltasten, Umschalt für größere S save = "Speichern" saveToServer = "Auf dem Server speichern" saveToServerDisabledHint = "Speichern auf dem Server ist auf diesem Server nicht aktiviert. Bitten Sie Ihren Administrator, es zu aktivieren." -search = "Suchen" -searchPlaceholder = "Diesen Ordner & Unterordner durchsuchen" selectAll = "Alle auswählen" selectAllHint = "Klicken, um alles auszuwählen. Tipp: Halten Sie Strg (oder Cmd), um Dateien einzeln hinzuzufügen, Umschalt zum Auswählen eines Bereichs." selectedCount = "{{count}} ausgewählt" @@ -4044,6 +4095,10 @@ title = "Noch keine Cloud-Dateien" hint = "Ohne Hochladen gespeicherte Dateien bleiben hier. Legen Sie eine Datei ab, um eine hinzuzufügen." title = "Keine nur lokalen Dateien" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Dateien, die Sie öffnen oder bearbeiten, erscheinen hier." title = "Noch nichts geändert" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Ordner konnte nicht gelöscht werden." deleteFolderFailedDetail = "Ordner konnte nicht gelöscht werden: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Ordner-Erscheinungsbild konnte nicht aktualisiert werden." folderAppearanceFailedDetail = "Ordner-Erscheinungsbild konnte nicht aktualisiert werden: {{message}}" moveFilesFailed = "Dateien konnten nicht verschoben werden." @@ -4113,6 +4170,11 @@ localHint = "Nur in diesem Browser gespeichert" shared = "Geteilt" sharedHint = "Über Link mit Ihnen geteilt" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Älteste zuerst" modifiedDesc = "Neueste zuerst" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Keine Dateien im Speicher verfügbar. Öffnen Sie zuerst open = "Öffnen" openFile = "Datei öffnen" openFiles = "Dateien öffnen" +selectFile = "Select {{name}}" selectFromStorage = "Aus Speicher auswählen" upload = "Hochladen" uploadFile = "Datei hochladen" @@ -4800,9 +4863,6 @@ title = "Bild zu PDF" [imageToPdf] tags = "konvertierung,img,jpg,bild,foto" -[infoBanner] -dismiss = "Ausblenden" - [invite] acceptError = "Konto konnte nicht erstellt werden" accountFor = "Konto wird erstellt für" @@ -4984,6 +5044,34 @@ uploadSuccess = "Upload Successful!" uploadSuccessMessage = "Ihre Bilder wurden übertragen." validating = "Validating session..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Scannen Sie, um Fotos hochzuladen. Bilder werden automatisch in PDF konvertiert." descriptionNoConvert = "Scannen Sie, um Fotos von Ihrem Mobilgerät hochzuladen." @@ -5002,8 +5090,13 @@ title = "Von Mobilgerät hochladen" tags = "Multi-Tool,Mehrfachoperation,UI,Klicken und Ziehen,Frontend,clientseitig" title = "PDF-Multitool" -[navbar] -search = "Suche" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Die Authentifizierung war nicht erfolgreich. Sie können dieses Fenster schließen und es erneut versuchen." @@ -5132,6 +5225,7 @@ activeFiles = "Die Ansicht Aktive Dateien zeigt alle PDFs, die allTools = "Dies ist das Panel Alle Tools, in dem Sie alle verfügbaren PDF-Tools durchsuchen und auswählen können." close = "Schließen" cropSettings = "Nachdem wir die Datei ausgewählt haben, die wir zuschneiden möchten, können wir das Tool Zuschneiden konfigurieren, um den Bereich auszuwählen, auf den das PDF zugeschnitten werden soll." +dialogLabel = "Onboarding" fileCheckbox = "Durch Klicken auf eine der Dateien wählen Sie diese zur Verarbeitung aus. Sie können mehrere Dateien für Batch-Operationen auswählen." fileReplacement = "Die geänderte Datei ersetzt automatisch die Originaldatei im Arbeitsbereich, sodass Sie sie einfach mit weiteren Tools verarbeiten können." filesButton = "Mit der Schaltfläche Dateien in der Schnellzugriffsleiste können Sie PDFs hochladen, um die Tools darauf anzuwenden." @@ -5568,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5647,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5975,6 +6071,11 @@ header = "PDF zu PDF/A" tags = "archiv,langfristig,standard,konvertierung,speicherung,aufbewahrung" title = "PDF zu PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "druck,standard,konvertierung,produktion,vorstufe,archiv" title = "PDF in PDF/X" @@ -6050,6 +6151,7 @@ small = "500 Credits" xsmall = "100 Credits" [plan.availablePlans] +currency = "Billing currency" subtitle = "Wählen Sie den Plan, der Ihren Anforderungen entspricht" title = "Verfügbare Pläne" @@ -6629,6 +6731,7 @@ reachedTitle = "Monatliches Ausgabenlimit erreicht" title = "Stripe-Portal konnte nicht geöffnet werden" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "von {{allowance}} kostenlosen PDFs genutzt" capSuffix_other = "von {{allowance}} kostenlosen PDFs genutzt" eyebrow = "Processor-Testversion" @@ -6878,6 +6981,32 @@ label = "Jira" description = "Ein Dokument oder eine Benachrichtigung per E-Mail senden, wenn eine Richtlinie ausgeführt wird." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Verarbeitete Dokumente in Nextcloud ablegen." @@ -7082,11 +7211,6 @@ title = "Offizielle SDKs" beta = "Beta" deprecated = "Veraltet" -[portal.docs.search] -empty = "Keine passenden Dokumente" -placeholder = "Dokumentation suchen" -results = "{{count}} Ergebnisse" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Gebündelte, benannte Fähigkeiten, die Ihr Agent als einzelnes Tool aufruft. Jeder Skill ist eine deterministische Operationskette mit angehängten Evals." @@ -7185,11 +7309,11 @@ editorAction = "Editor" empty = "Keine Dokumente entsprechen diesem Filter." rowActions = "Zeilenaktionen" sensitiveLabel = "Sensibel" -sensitiveTitle = "Sensibel — Zugriff erforderlich" [portal.documents.table.columns] action = "Pipeline / Aktion" document = "Dokument" +labels = "Labels" product = "Produkt" status = "Status" time = "Zeit" @@ -7205,6 +7329,7 @@ host = "Host" lastSeen = "Zuletzt gesehen" region = "Region" status = "Status" +target = "Target" version = "Version" [portal.editorAdmin.health.empty] @@ -7279,6 +7404,56 @@ description = "In dieser Ansicht ist ein unerwarteter Fehler aufgetreten. Versuc retry = "Erneut versuchen" title = "Auf dieser Seite ist etwas schiefgelaufen" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Zurück" body = "Laden Sie die Desktop-App herunter oder hosten Sie den Server selbst." @@ -7385,11 +7560,6 @@ cancel = "Abbrechen" confirm = "Schlüssel widerrufen" title = "API-Schlüssel widerrufen" -[portal.infrastructure.attestationLabel] -attested = "Bestätigt" -inScope = "Im Geltungsbereich" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Audit-Ereignisse nach Kategorie filtern" heading = "Audit-Protokolle" @@ -7461,11 +7631,6 @@ info = "Info" success = "Erfolg" warning = "Warnung" -[portal.infrastructure.certLabel] -certified = "Zertifiziert" -inProgress = "In Bearbeitung" -notStarted = "Nicht gestartet" - [portal.infrastructure.createKey] cancel = "Abbrechen" createKey = "Schlüssel erstellen" @@ -7479,238 +7644,10 @@ subtitleCreated = "Kopieren Sie dieses Secret jetzt — es wird nicht erneut ang title = "API-Schlüssel erstellen" titleCreated = "Schlüssel erstellt" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "In Warteschlange" -rolledBack = "Zurückgesetzt" -rolling = "Wird ausgerollt" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Bereitgestellt von" -environment = "Umgebung" -product = "Produkt" -status = "Status" -version = "Version" -when = "Wann" - -[portal.infrastructure.deployments.recent] -heading = "Letzte Bereitstellungen" -subheading = "Die neuesten Rollouts über Produkte und Umgebungen hinweg." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instanzen" -latency = "Latenz" -load = "Last" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Durchsatz" -uptime = "Uptime" -version = "Version" - -[portal.infrastructure.deployments.regions] -heading = "Regionen" -subheading = "Live-Zustand für jede bereitgestellte Stirling-Region — Latenz, Last und Rollout-Version." - -[portal.infrastructure.deployments.regions.empty] -description = "Bereitgestellte Regionen erscheinen hier, sobald Ihr Arbeitsbereich bereitgestellt ist." -title = "Keine Regionen bereitgestellt" - [portal.infrastructure.keyLabel] active = "Aktiv" revoked = "Widerrufen" -[portal.infrastructure.modelLabel] -active = "Aktiv" -degraded = "Beeinträchtigt" -disabled = "Deaktiviert" - -[portal.infrastructure.models] -heading = "Modelle" -msValue = "{{value}} ms" -subheading = "Der Modellkatalog und das Routing, die die Dokumentverarbeitung in Ihrem Arbeitsbereich antreiben." - -[portal.infrastructure.models.byom] -description = "Registrieren Sie ein On-Prem- oder selbst gehostetes Modell und pinnen Sie es an eine Region für datenresidenzgebundene Verarbeitung." -title = "Eigenes Modell verwenden" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Verwaltete Modelle, die Ihrem Arbeitsbereich zur Verfügung stehen, mit Live-Latenz und Kosten." -subEnterprise = "Verwaltete, eigene und On-Prem-Modelle — mit verfügbarem Pinning pro Region." - -[portal.infrastructure.models.catalogue.empty] -description = "Modelle im Katalog Ihres Arbeitsbereichs erscheinen hier." -title = "Keine Modelle verfügbar" - -[portal.infrastructure.models.columns] -cost = "Kosten" -latency = "Latenz" -load = "Last" -model = "Modell" -status = "Status" -type = "Typ" -version = "Version" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/Aufruf" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktive Modelle" -avgLatency = "Durchschn. Latenz" -included = "Enthalten" -monthlySpend = "Monatliche Modellausgaben" - -[portal.infrastructure.models.routing] -empty = "Keine Routing-Regeln konfiguriert." -heading = "Routing-Regeln" -sub = "Welches Modell welche Operation verarbeitet. Die Standardeinstellung gilt, wenn keine spezifischere Regel passt." -subLocked = "Leiten Sie Operationen an bestimmte Modelle weiter — verfügbar in kostenpflichtigen Plänen." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Upgraden Sie auf Pro, um zu steuern, welches Modell welche Operation und welchen Dokumenttyp verarbeitet." -title = "Modell-Routing ist eine kostenpflichtige Funktion" - -[portal.infrastructure.models.routingColumns] -default = "Standard" -docType = "Dokumenttyp" -modelForAria = "Modell für {{operation}}" -operation = "Operation" -routedTo = "Weitergeleitet an" - -[portal.infrastructure.modelTypeLabel] -classification = "Klassifizierung" -extraction = "Extraktion" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Beeinträchtigt" -down = "Ausgefallen" -healthy = "Fehlerfrei" - -[portal.infrastructure.security.access.byok] -description = "Stellen Sie einen Schlüssel aus Ihrem eigenen KMS bereit. Stirling verschlüsselt damit, kann aber weiterhin lesen." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Schlüssel verlassen niemals Ihr KMS. Stirling hält nur Ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling verwaltet Verschlüsselungsschlüssel. Am einfachsten — kein Schlüsselbetrieb auf Ihrer Seite." -label = "Von Stirling gehaltene Schlüssel" - -[portal.infrastructure.security.accessPolicy] -heading = "Dokumentzugriffsrichtlinie" -subheading = "Steuert, wer verarbeitete ruhende Dokumente entschlüsseln kann." - -[portal.infrastructure.security.attestations] -heading = "Compliance-Attestierungen" -noReport = "Kein Bericht verfügbar" -subheading = "Audit-Status nach Framework, mit Berichten zu attestierten Kontrollen." -viewReport = "Bericht anzeigen →" - -[portal.infrastructure.security.compliance] -heading = "Compliance" -subheading = "Attestierungen und Zertifizierungen für die Stirling-Plattform." - -[portal.infrastructure.security.empty] -description = "Die Sicherheitskonfiguration Ihres Arbeitsbereichs erscheint hier." -title = "Sicherheitsstatus nicht verfügbar" - -[portal.infrastructure.security.hyokBanner] -description = "Mit HYOK verlassen Verschlüsselungsschlüssel niemals Ihr KMS. Stirling speichert und verarbeitet nur Ciphertext, den Sie jederzeit widerrufen können." -title = "Stirling kann Ihre Dokumente nicht entschlüsseln" - -[portal.infrastructure.security.ipAllowlist] -empty = "Keine IP-Bereiche konfiguriert — alle IPs erlaubt." -heading = "IP-Allowlist" -sub = "API-Zugriff ist auf diese CIDR-Bereiche beschränkt." -subLocked = "Beschränken Sie API-Zugriff auf bekannte IP-Bereiche — verfügbar in kostenpflichtigen Plänen." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Upgraden Sie auf Pro, um API-Zugriff auf bestimmte Netzwerke zu beschränken." -title = "IP-Allowlisting ist eine kostenpflichtige Funktion" - -[portal.infrastructure.security.ipColumns] -added = "Hinzugefügt" -addedBy = "Hinzugefügt von" -cidr = "CIDR" -label = "Label" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithmus" -heading = "Verwaltung von Verschlüsselungsschlüsseln" -keyId = "Schlüsselkennung" -lastRotated = "Zuletzt rotiert" -rotateKey = "Schlüssel rotieren" -rotationPolicy = "Rotationsrichtlinie" -subheading = "Verwahrung der Schlüssel, die ruhende Dokumente verschlüsseln — wer entschlüsseln kann und wie Schlüssel rotieren." - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key (BYOK) und hold-your-own-key (HYOK) sind in Enterprise verfügbar. Upgraden Sie, um Schlüssel aus Ihrem eigenen KMS bereitzustellen." -title = "Schlüssel werden in Ihrem Plan von Stirling verwaltet" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asien-Pazifik" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR-Datengrenze" -label = "Europäische Union" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Vereinigte Staaten" - -[portal.infrastructure.security.residencyHeader] -heading = "Datenresidenz" -subheading = "Wo Dokumente gespeichert und verarbeitet werden." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} genutzt" - -[portal.infrastructure.storage.empty] -description = "Verbundener Speicher und Nutzung erscheinen hier." -title = "Kein Speicher konfiguriert" - -[portal.infrastructure.storage.lifecycle] -active = "Aktiv" -activeRange = "0–{{value}} T" -archived = "Archiviert" -coldStorage = "Cold Storage" -deleted = "Gelöscht" -never = "nie" -purged = "endgültig gelöscht" - -[portal.infrastructure.storage.providers] -connect = "Verbinden" -connected = "Verbunden" -heading = "Verbundene Anbieter" -subheading = "Wohin verarbeitete Artefakte geschrieben werden." - -[portal.infrastructure.storage.retention] -heading = "Aufbewahrung" -subheading = "Wie lange Artefakte vor der Lifecycle-Löschung aufbewahrt werden." -windowLabel = "Standard-Aufbewahrungsfenster" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} Tag" -days_other = "{{count}} Tage" -never = "Nie löschen" - -[portal.infrastructure.storage.totalUsage] -heading = "Gesamtnutzung" -progressLabel = "Genutzter Speicher" -subheading = "Speicherverbrauch über alle verbundenen Anbieter hinweg." - [portal.infrastructure.tabs] apiKeys = "API-Schlüssel" audit = "Audit-Protokolle" @@ -7720,11 +7657,9 @@ security = "Sicherheit" storage = "Speicher" [portal.integrations] -addAnother = "Weitere hinzufügen" availableHeading = "Verfügbar" comingSoonHeading = "Demnächst" connect = "Verbinden" -connectedHeading = "Verbunden" connectionCount_one = "{{count}} Verbindung" connectionCount_other = "{{count}} Verbindungen" customApi = "Benutzerdefinierte API" @@ -7742,6 +7677,10 @@ security = "Sicherheit" signing = "Signieren" storage = "Speicher" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Verbunden" @@ -7772,7 +7711,6 @@ integrations = "Integrationen" pipelines = "Pipelines" policies = "Richtlinien" procurement = "Beschaffung" -settings = "Einstellungen" sources = "Quellen" usage = "Nutzung & Abrechnung" users = "Benutzer" @@ -7789,7 +7727,7 @@ title = "Pipelines" newPipeline = "Neue Pipeline" [portal.pipelines.builder] -addStep = "Tool hinzufügen" +activate = "Activate" back = "Zurück zu Pipelines" cannotFollow = "Kann {{produced}} nicht übernehmen" chooseAccount = "Konto auswählen" @@ -7797,28 +7735,38 @@ chooseDestination = "Ziel auswählen" chooseOperation = "Auswählen, was dieser Schritt tut" chooseSource = "Quelle auswählen" discard = "Änderungen verwerfen" -enabled = "Aktiviert" inputs = "Eingabe" inputSource = "Eingabequelle" inputTrigger = "Trigger" keepEditing = "Weiter bearbeiten" +moreActions = "More actions" needsConfiguring = "Einrichtung erforderlich" -needsUpload = "Benötigt eine hochgeladene Datei" -noSources = "Noch keine Quellen verbunden. Erstellen Sie eine, um sie als Eingabe zu verwenden." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Keine Tools entsprechen Ihrer Suche." -pipelineSettings = "Pipeline-Einstellungen" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Tools suchen" -selectToolBody = "Fügen Sie ein Tool hinzu, um Ihre Pipeline zu erstellen." -selectToolTitle = "Noch keine Tools" sendToSystem = "An ein anderes System senden" stepsIncompatible = "Diese Schritte können nicht mit dem ausgeführt werden, was der vorherige Schritt erzeugt: {{tools}}." stepsNeedSetup = "Diese Schritte müssen vor dem Speichern noch eingerichtet werden: {{tools}}." -toolSettings = "Tool-Einstellungen" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Nicht erkannte Operation, unverändert beibehalten." unsavedBody = "Sie haben ungespeicherte Änderungen. Speichern Sie sie vor dem Verlassen oder verwerfen Sie sie?" unsavedTitle = "Ungespeicherte Änderungen" -uploadUnsupported = "Hochgeladene Dateien werden in Pipelines noch nicht unterstützt, daher können diese Schritte nicht gespeichert werden: {{tools}}." usesDefaults = "Läuft mit Standardeinstellungen" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kombiniert jede Datei aus dem vorherigen Schritt" @@ -7830,31 +7778,36 @@ undeclared-operation = "Kann nicht prüfen, was dieser Schritt akzeptiert" [portal.pipelines.composer] addTool = "Tool hinzufügen" -cancel = "Abbrechen" -chainEmpty = "Fügen Sie ein Tool hinzu, um mit dem Erstellen Ihrer Pipeline zu beginnen." create = "Pipeline erstellen" +createPaused = "Create paused" editingUnsupported = "Die Anzeige dieser Tool-Parameter zur Bearbeitung wird noch nicht unterstützt." -moveDown = "Nach unten verschieben" -moveUp = "Nach oben verschieben" +editSource = "Edit source" name = "Name" namePlaceholder = "z. B. Schwärzungsprüfung" noToolSettings = "Dieses Tool hat keine konfigurierbaren Einstellungen." -operations_one = "Operation ({{count}})" -operations_other = "Operationen ({{count}})" output = "Ausgabe" -removeStep = "Operation entfernen" save = "Änderungen speichern" scheduleEvery = "Ausführen alle" -sources = "Quellen" -sourcesLoading = "Quellen werden geladen..." trigger = "Trigger" triggerManual = "Nur manuell" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "Tage" hours = "Stunden" minutes = "Minuten" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\" löschen? Dies kann nicht rückgängig gemacht werden." cancel = "Abbrechen" @@ -7872,6 +7825,37 @@ connectSource = "Quelle verbinden" description = "Erstellen Sie Ihre erste Pipeline: Wählen Sie die Quellen aus, über die sie läuft, verketten Sie die Operationen und wählen Sie, wohin die Ausgabe geht." title = "Noch keine Pipelines" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktiv" paused = "Pausiert" @@ -7884,7 +7868,6 @@ completed_one = "Ausführung abgeschlossen." completed_other = "Alle {{count}} Ausführungen abgeschlossen." empty = "Nichts auszuführen: Die Quellen hatten keine zu verarbeitenden Dokumente." failed = "Ausführung fehlgeschlagen: {{error}}" -historyCleared = "Verlauf gelöscht. Der nächste Lauf verarbeitet alles erneut, was sich derzeit in den Quellen befindet." inFlight = "Nichts Neues auszuführen: Dokumente werden noch aus einem früheren Lauf verarbeitet." parked_one = "Nichts auszuführen: {{count}} Dokument ist zuvor fehlgeschlagen und geparkt. Beheben Sie die Ursache und löschen Sie dann den Verlauf, um es erneut zu versuchen." parked_other = "Nichts auszuführen: {{count}} Dokumente sind zuvor fehlgeschlagen und geparkt. Beheben Sie die Ursache und löschen Sie dann den Verlauf, um sie erneut zu versuchen." @@ -7900,6 +7883,7 @@ name = "Pipeline" sources = "Quellen" status = "Status" steps = "Schritte" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Ordnerüberwachung" @@ -8214,6 +8198,24 @@ label = "An einen Jira-Issue anhängen" description = "Sendet das verarbeitete Dokument als Anhang." label = "Dokument per E-Mail senden (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Schreibt das verarbeitete Dokument in einen Ordner." label = "In Nextcloud hochladen" @@ -8688,16 +8690,6 @@ maxed = "Ausgeschöpft" subtitle = "Ihre kostenlose Testversion läuft bis {{date}}. Keine Karte erforderlich." title = "Enterprise-Testversion" -[portal.search] -ariaLabel = "Suchen" -placeholder = "Stirling durchsuchen — Endpoints, Pipelines, Dokumente…" - -[portal.search.empty] -noActionsDescription = "Schnellaktionen werden hier angezeigt, sobald sie verfügbar sind." -noActionsTitle = "Keine Schnellaktionen" -noMatches = "Keine Treffer für „{{query}}“" -noMatchesDescription = "Versuchen Sie ein anderes Schlüsselwort oder durchsuchen Sie den Katalog." - [portal.settings.groups] admin = "Admin" @@ -8748,10 +8740,6 @@ cancel = "Abbrechen" confirm = "Löschen" title = "Quelle löschen?" -[portal.sources.empty] -description = "Verbinden Sie einen Ordner (und bald Cloud-Speicher), damit Ihre Richtlinien einen Ort haben, von dem sie Dokumente abrufen können." -title = "Noch keine Quellen verbunden" - [portal.sources.kpi] inUse = "In Verwendung" total = "Verbindungen" @@ -8790,6 +8778,7 @@ unused = "Unbenutzt" documents = "Dokumente" source = "Quelle" status = "Status" +type = "Type" usedBy = "Richtlinien" [portal.sources.types.box] @@ -9985,9 +9974,6 @@ title = "Richtlinien & Datenschutz" [settings.preferences] title = "Einstellungen" -[settings.search] -placeholder = "Einstellungen durchsuchen..." - [settings.security] description = "Aktualisieren Sie Ihr Passwort, um Ihr Konto zu schützen." title = "Sicherheit" @@ -10133,6 +10119,7 @@ last = "Letzte Seite" maintainRatio = "Seitenverhältnis beibehalten ein-/ausschalten" next = "Nächste Seite" noSavedSigs = "Es wurden keine gespeicherten Signaturen gefunden" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Persönliche Signaturen" previous = "Vorherige Seite" redo = "Wiederherstellen" @@ -10171,6 +10158,7 @@ backgroundRemovalFailedTitle = "Hintergrund konnte nicht entfernt werden" hint = "Laden Sie ein PNG- oder JPG-Bild Ihrer Unterschrift hoch" label = "Unterschriftsbild hochladen" placeholder = "Bilddatei auswählen" +previewAlt = "Current image signature" processing = "Bild wird verarbeitet..." removeBackground = "Weißen Hintergrund entfernen (transparent machen)" @@ -10184,6 +10172,17 @@ saved = "Wählen Sie oben eine gespeicherte Unterschrift und klicken Sie dann in text = "Nachdem Sie oben Ihren Namen eingegeben haben, klicken Sie irgendwo auf das PDF, um Ihre Signatur zu platzieren." title = "So fügen Sie eine Unterschrift hinzu" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Signatur bewegen" pause = "Platzieren pausieren" @@ -10630,6 +10629,23 @@ title = "Auf Server hochladen" updateButton = "Auf Server aktualisieren" uploadButton = "Auf Server hochladen" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10779,7 +10795,6 @@ goBack = "Zurück" pdfTools = "PDF-Tools" placeholder = "Wählen Sie ein Tool, um zu starten" premiumFeature = "Premium-Funktion:" -searchTools = "Tools suchen" toolsHeader = "Tools" viewAllTools = "Alle Tools anzeigen" @@ -10827,6 +10842,9 @@ removal = "Entfernung" signing = "Signierung" verification = "Verifizierung" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Keine Tools gefunden" noTools = "Keine Tools verfügbar" @@ -10984,6 +11002,13 @@ approver = "Genehmigt Richtlinie" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Einladung an {{email}} zurückziehen? Diese Person kann dem Team mit dem aktuellen Link nicht mehr beitreten." cancelInviteTitle = "Einladung zurückziehen" @@ -11003,10 +11028,8 @@ title = "Noch keine Mitglieder" addToTeam = "Zum Team hinzufügen" guestCount = "{{count}} Gast" guests = "Gäste" -guestsDesc = "Externe Mitwirkende, beschränkt auf das, was Sie geteilt haben. Nur Editor." ledBy = "geleitet von {{owner}}" org = "Organisation" -orgDesc = "Inhaber mit organisationsweiter Befugnis und Richtliniengenehmigung" owners = "{{count}} Inhaber" team = "Team {{name}}" teamMeta = "{{count}} Personen" @@ -11046,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Eingeladen von {{who}}" cancel = "Zurückziehen" -count = "{{count}} ausstehend" -desc = "Eingeladene Personen, die noch nicht beigetreten sind. Sie belegen einen Platz, bis sie die Einladung annehmen." expiresInDays_one = "Läuft in {{count}} Tag ab" expiresInDays_other = "Läuft in {{count}} Tagen ab" expiresToday = "Läuft heute ab" title = "Ausstehende Einladungen" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Beim Zugriff auf das Backend ist etwas schiefgelaufen, oder Sie haben keinen Zugriff. Versuchen Sie es erneut." title = "Mitglieder konnten nicht geladen werden" @@ -11242,12 +11267,15 @@ searchPlaceholder = "Search attachments" title = "Attachments" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Lesezeichentitel" bookmarkTitleRequired = "Ein Lesezeichentitel ist erforderlich" closeSidebar = "Seitenleiste für Lesezeichen schließen" collapseAll = "Alle Lesezeichen einklappen" +empty = "No bookmarks in this document" expandAll = "Alle Lesezeichen ausklappen" searchPlaceholder = "Lesezeichen durchsuchen" +title = "Bookmarks" [viewer.comments] addComment = "Kommentar hinzufügen" @@ -11269,10 +11297,12 @@ locateAnnotation = "Im Dokument anzeigen" moreActions = "Weitere Aktionen" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Seite {{page}}" placingHint = "Zum Platzieren auf eine Seite klicken… (Abbrechen)" removeCommentOnly = "Nur Kommentar entfernen" saveReply = "Antwort speichern" +searchPlaceholder = "Search comments" title = "Kommentare" typeComment = "Kommentar" typeInsertText = "Text einfügen" @@ -11295,6 +11325,7 @@ unsavedDesc = "Sie haben ungespeicherte Änderungen" closeSidebar = "Seitenleiste für Ebenen schließen" hideAll = "Alle Ebenen ausblenden" showAll = "Alle Ebenen anzeigen" +title = "Layers" [viewer.link] delete = "Link löschen" @@ -11651,6 +11682,7 @@ exitRedaction = "Schwärzungsmodus beenden" exportAll = "PDF exportieren" exportSelected = "Ausgewählte Seiten exportieren" formFill = "Formular ausfüllen" +hideToolbar = "Hide toolbar" multiTool = "Multitool" panMode = "Verschiebemodus" print = "PDF drucken" @@ -11671,6 +11703,7 @@ selectAll = "Alle auswählen" selectByNumber = "Nach Seitenzahlen auswählen" selectLanguage = "Sprache auswählen" share = "Teilen" +showToolbar = "Show toolbar" toggleAnnotations = "Anmerkungen ein-/ausblenden" toggleAttachments = "Anhänge ein-/ausblenden" toggleBookmarks = "Lesezeichen ein-/ausblenden" diff --git a/frontend/editor/public/locales/el-GR/translation.toml b/frontend/editor/public/locales/el-GR/translation.toml index 9b40dd49ca..4c380b9447 100644 --- a/frontend/editor/public/locales/el-GR/translation.toml +++ b/frontend/editor/public/locales/el-GR/translation.toml @@ -1,7 +1,6 @@ alphabet = "Αλφάβητο" apply = "Εφαρμογή" applyAndContinue = "Αποθήκευση & Έξοδος" -areYouSure = "Είστε βέβαιοι ότι θέλετε να αποχωρήσετε;" back = "Πίσω" black = "Μαύρο" blue = "Μπλε" @@ -106,6 +105,7 @@ undoSuccess = "Η ενέργεια αναιρέθηκε με επιτυχία" unknown = "Άγνωστο" unpin = "Ξεκαρφίτσωμα αρχείου (αντικατάσταση μετά την εκτέλεση εργαλείου)" unsavedChanges = "Έχετε μη αποθηκευμένες αλλαγές στο PDF σας." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Μη αποθηκευμένες αλλαγές" unsupported = "Μη υποστηριζόμενο" username = "Όνομα χρήστη" @@ -1879,6 +1879,9 @@ width = "Πλάτος" [app] description = "Η δωρεάν εναλλακτική του Adobe Acrobat (10M+ λήψεις)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Μετατροπή σε PDF/A-3b" convertToPdfA3bDescription = "Δημιουργεί αρχειακό PDF με ενσωματωμένα συνημμένα" @@ -2933,12 +2936,16 @@ text = "Για να καταστούν αυτά τα δικαιώματα μη [chat.actions] copy = "Αντιγραφή μηνύματος" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Επιλογές πράκτορα Stirling" +assistant = "AI Assistant" clearChat = "Καθαρισμός συνομιλίας" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Αυτή η λειτουργία θα χρησιμοποιήσει τ tooltip = "Επιλογή χρώματος από την οθόνη" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Επιλέξτε χρώμα" [common] +actions = "Actions" back = "Πίσω" cancel = "Άκυρο" close = "Κλείσιμο" +codeSample = "Code sample" collapse = "Σύμπτυξη" confirm = "Επιβεβαίωση" continue = "Συνέχεια" copied = "Αντιγράφηκε!" copy = "Αντιγραφή" done = "Τέλος" +enterValue = "Enter value" error = "Σφάλμα" expand = "Ανάπτυξη" loading = "Φόρτωση..." next = "Επόμενο" +open = "Open" preview = "Προεπισκόπηση" previous = "Προηγούμενο" refresh = "Ανανέωση" remaining = "Απομένουν" retry = "Επανάληψη" save = "Αποθήκευση" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Καθαρισμός επιλεγμένων" @@ -3185,6 +3199,7 @@ title = "Μέθοδος συμπίεσης" [compress.settings] desiredSize = "Επιθυμητό μέγεθος αρχείου" desiredSizePlaceholder = "Εισαγάγετε μέγεθος" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Η συμπίεση είναι ένας εύκολος τρόπος να μειώσετε το μέγεθος του αρχείου σας. Επιλέξτε Μέγεθος αρχείου για να εισαγάγετε έναν στόχο και να προσαρμόσουμε την ποιότητα για εσάς. Επιλέξτε Ποιότητα για να ρυθμίσετε χειροκίνητα την ισχύ συμπίεσης." @@ -3387,6 +3402,24 @@ pdfOptions = "Επιλογές PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Επιλέξτε πρώτα μορφή προέλευσης" settings = "Ρυθμίσεις" single = "Μονό" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Αναζήτηση..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Επεξεργασία" @@ -3824,11 +3861,13 @@ shareSelected = "Κοινή χρήση επιλεγμένων" sharing = "Κοινή χρήση" showAll = "Προβολή όλων" showHistory = "Εμφάνιση ιστορικού" +sortBy = "Sort files" sortByDate = "Ταξινόμηση κατά ημερομηνία" sortByName = "Ταξινόμηση κατά όνομα" sortBySize = "Ταξινόμηση κατά μέγεθος" storage = "Αποθήκευση" storageState = "Αποθήκευση" +storageUsed = "Storage used" synced = "Συγχρονισμένο" title = "Μεταφόρτωση αρχείων PDF" toolChain = "Εργαλεία που εφαρμόστηκαν" @@ -3874,8 +3913,12 @@ addFiles = "Προσθήκη αρχείων" addingFiles = "Προσθήκη αρχείων…" collapse = "Σύμπτυξη πλευρικής στήλης" customizeGroups = "Προσαρμογή ομάδων" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Ανοίξτε αρχεία για να ξεκινήσετε" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Ανάπτυξη πλευρικής στήλης" googleDrive = "Google Drive" googleDriveDisabled = "Το Google Drive δεν έχει ρυθμιστεί" @@ -3888,15 +3931,17 @@ openFromComputer = "Άνοιγμα από υπολογιστή" openSettings = "Άνοιγμα ρυθμίσεων" other = "Άλλα" recent = "Πρόσφατα" -search = "Αναζήτηση" -searchPlaceholder = "Αναζήτηση αρχείων..." viewAll = "Προβολή όλων των {{count}} αρχείων" [fileSidebar.fileItem] closeViewer = "Κλείσιμο προβολέα" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Άνοιγμα στον προβολέα" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Επαναφορά προεπιλογών" subtitle = "Ομαδοποιήστε τα αρχεία σας σε γονικές κατηγορίες. Προσθέστε υπάρχουσες ή νέες ετικέτες σε μια κατηγορία, μετονομάστε την ή δημιουργήστε τη δική σας. Τα αρχεία που δεν ανήκουν σε καμία από τις κατηγορίες σας εμφανίζονται στην ενότητα «Άλλα»." title = "Κατηγορίες πλαϊνής γραμμής" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Προσθήκη σε χώρο εργασίας" addToWorkspaceCount = "Προσθήκη {{count}} σε χώρο εργασίας" @@ -3918,7 +3971,6 @@ backToMyFiles = "Πίσω στα Αρχεία μου" breadcrumbs = "Διαδρομή φακέλου" cancel = "Άκυρο" classification = "Ταξινόμηση" -clearSearch = "Καθαρισμός αναζήτησης" clearSelection = "Εκκαθάριση επιλογής" closeDetails = "Κλείσιμο λεπτομερειών" create = "Δημιουργία" @@ -3950,6 +4002,7 @@ downloadAll = "Λήψη όλων" downloadVersion = "Λήψη αυτής της έκδοσης" dropOverlay = "Αποθέστε αρχεία για μεταφόρτωση" dropOverlaySub = "Τα αρχεία ξεκινούν στο Local. Χρησιμοποιήστε 'Μετακίνηση σε' ή 'Αποθήκευση στο cloud' για να τα οργανώσετε σε φάκελο." +duplicate = "Duplicate" file = "Αρχείο" fileInfo = "File info" fileMenu = "Ενέργειες αρχείου" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Αλλαγή μεγέθους δέντρου φακέλων save = "Αποθήκευση" saveToServer = "Αποθήκευση στον διακομιστή" saveToServerDisabledHint = "Η αποθήκευση στον διακομιστή δεν είναι ενεργοποιημένη σε αυτόν τον διακομιστή. Ζητήστε από τον διαχειριστή σας να την ενεργοποιήσει." -search = "Αναζήτηση" -searchPlaceholder = "Αναζήτηση σε αυτόν τον φάκελο & υποφακέλους" selectAll = "Επιλογή όλων" selectAllHint = "Κάντε κλικ για επιλογή όλων. Συμβουλή: κρατήστε πατημένο το Ctrl (ή Cmd) για να προσθέτετε αρχεία ένα-ένα, Shift για επιλογή εύρους." selectedCount = "{{count}} επιλεγμένα" @@ -4044,6 +4095,10 @@ title = "Δεν υπάρχουν ακόμη αρχεία cloud" hint = "Αρχεία που αποθηκεύτηκαν χωρίς μεταφόρτωση μένουν εδώ. Αποθέστε ένα αρχείο για προσθήκη." title = "Δεν υπάρχουν μόνο-τοπικά αρχεία" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Αρχεία που ανοίγετε ή επεξεργάζεστε θα εμφανιστούν εδώ." title = "Δεν έχει τροποποιηθεί ακόμα τίποτα" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Δεν ήταν δυνατή η διαγραφή φακέλου." deleteFolderFailedDetail = "Δεν ήταν δυνατή η διαγραφή φακέλου: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Δεν ήταν δυνατή η ενημέρωση εμφάνισης φακέλου." folderAppearanceFailedDetail = "Δεν ήταν δυνατή η ενημέρωση εμφάνισης φακέλου: {{message}}" moveFilesFailed = "Δεν ήταν δυνατή η μετακίνηση αρχείων." @@ -4113,6 +4170,11 @@ localHint = "Αποθηκευμένο μόνο σε αυτό το πρόγραμ shared = "Κοινοποιημένο" sharedHint = "Κοινοποιήθηκε σε εσάς μέσω συνδέσμου" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Παλαιότερα πρώτα" modifiedDesc = "Πιο πρόσφατα πρώτα" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Δεν υπάρχουν αρχεία στην αποθή open = "Άνοιγμα" openFile = "Άνοιγμα αρχείου" openFiles = "Άνοιγμα αρχείων" +selectFile = "Select {{name}}" selectFromStorage = "Επιλογή από αποθήκευση" upload = "Μεταφόρτωση" uploadFile = "Μεταφόρτωση αρχείου" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Όλα αποθηκεύτηκαν" +analyzingFields = "Analysing form fields..." extractCsvError = "Απέτυχε η εξαγωγή CSV" extractXlsxError = "Απέτυχε η εξαγωγή XLSX" +filled = "filled" flattenAfterFilling = "Επιπεδοποίηση μετά τη συμπλήρωση" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "απαιτ." requiredFieldsError = "Συμπληρώστε όλα τα απαιτούμενα πεδία" rescanFields = "Επανασάρωση πεδίων" @@ -4795,9 +4863,6 @@ title = "Εικόνα σε PDF" [imageToPdf] tags = "μετατροπή,εικόνα,jpg,φωτογραφία" -[infoBanner] -dismiss = "Απόρριψη" - [invite] acceptError = "Αποτυχία δημιουργίας λογαριασμού" accountFor = "Δημιουργία λογαριασμού για" @@ -4979,6 +5044,34 @@ uploadSuccess = "Η μεταφόρτωση ολοκληρώθηκε!" uploadSuccessMessage = "Οι εικόνες σας μεταφέρθηκαν." validating = "Γίνεται επικύρωση συνεδρίας..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Σαρώστε για μεταφόρτωση φωτογραφιών. Οι εικόνες μετατρέπονται αυτόματα σε PDF." descriptionNoConvert = "Σαρώστε για μεταφόρτωση φωτογραφιών από την κινητή συσκευή σας." @@ -4997,8 +5090,13 @@ title = "Μεταφόρτωση από κινητό" tags = "Πολυεργαλείο,Πολλαπλές λειτουργίες,UI,κλικ και σύρσιμο,frontend,πλευρά πελάτη,διαδραστικό,διαχειρίσιμο,μετακίνηση,διαγραφή,μετανάστευση,διαίρεση" title = "Πολυεργαλείο PDF" -[navbar] -search = "Αναζήτηση" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Η ταυτοποίηση δεν ήταν επιτυχής. Μπορείτε να κλείσετε αυτό το παράθυρο και να δοκιμάσετε ξανά." @@ -5127,6 +5225,7 @@ activeFiles = "Η προβολή Ενεργά αρχεία εμ allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Κλείσιμο" cropSettings = "Τώρα που επιλέξαμε το αρχείο που θέλουμε να περικόψουμε, μπορούμε να ρυθμίσουμε το εργαλείο Περικοπή για να ορίσουμε την περιοχή στην οποία θέλουμε να περικοπεί το PDF." +dialogLabel = "Onboarding" fileCheckbox = "Κάνοντας κλικ σε ένα από τα αρχεία το επιλέγετε για επεξεργασία. Μπορείτε να επιλέξετε πολλαπλά αρχεία για ομαδικές ενέργειες." fileReplacement = "Το τροποποιημένο αρχείο θα αντικαταστήσει αυτόματα το πρωτότυπο στην Επιφάνεια εργασίας, ώστε να μπορείτε εύκολα να το περάσετε από περισσότερα εργαλεία." filesButton = "Το κουμπί Αρχεία στη Γραμμή Γρήγορης Πρόσβασης σάς επιτρέπει να ανεβάσετε PDF για χρήση με τα εργαλεία." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF σε PDF/A" tags = "αρχείο,μακροχρόνιο,πρότυπο,μετατροπή,αποθήκευση,διατήρηση" title = "PDF σε PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "εκτύπωση,πρότυπο,μετατροπή,παραγωγή,προεκτύπωση,αρχειοθέτηση" title = "PDF σε PDF/X" @@ -6045,6 +6151,7 @@ small = "500 μονάδες" xsmall = "100 μονάδες" [plan.availablePlans] +currency = "Billing currency" subtitle = "Επιλέξτε το πλάνο που ταιριάζει στις ανάγκες σας" title = "Διαθέσιμα πλάνα" @@ -6624,6 +6731,7 @@ reachedTitle = "Το μηνιαίο όριο δαπανών επιτεύχθηκ title = "Δεν ήταν δυνατό το άνοιγμα του portal του Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "από {{allowance}} δωρεάν PDFs χρησιμοποιήθηκε" capSuffix_other = "από {{allowance}} δωρεάν PDFs χρησιμοποιήθηκαν" eyebrow = "Δοκιμή Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Αποστολή email με έγγραφο ή ειδοποίηση όταν εκτελείται μια πολιτική." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Αρχειοθέτηση επεξεργασμένων εγγράφων στο Nextcloud." @@ -7077,11 +7211,6 @@ title = "Επίσημα SDKs" beta = "Beta" deprecated = "Καταργημένο" -[portal.docs.search] -empty = "Δεν υπάρχουν αντίστοιχα docs" -placeholder = "Αναζήτηση docs" -results = "{{count}} αποτελέσματα" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Συσκευασμένες, ονομασμένες δυνατότητες που ο πράκτοράς σας καλεί ως ένα ενιαίο εργαλείο. Κάθε skill είναι μια ντετερμινιστική αλυσίδα λειτουργιών με συνδεδεμένα evals." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Κανένα έγγραφο δεν αντιστοιχεί σε αυτό το φίλτρο." rowActions = "Ενέργειες γραμμής" sensitiveLabel = "Ευαίσθητο" -sensitiveTitle = "Ευαίσθητο — απαιτείται πρόσβαση" [portal.documents.table.columns] action = "Ροή εργασίας / Ενέργεια" document = "Έγγραφο" +labels = "Labels" product = "Προϊόν" status = "Κατάσταση" time = "Ώρα" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Τελευταία εμφάνιση" region = "Περιοχή" status = "Κατάσταση" +target = "Target" version = "Έκδοση" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Αυτή η προβολή αντιμετώπισε ένα απρ retry = "Δοκιμάστε ξανά" title = "Κάτι πήγε στραβά σε αυτήν τη σελίδα" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Πίσω" body = "Κατεβάστε την εφαρμογή desktop ή φιλοξενήστε μόνοι σας τον server." @@ -7380,11 +7560,6 @@ cancel = "Ακύρωση" confirm = "Ανάκληση κλειδιού" title = "Ανάκληση API κλειδιού" -[portal.infrastructure.attestationLabel] -attested = "Βεβαιωμένο" -inScope = "Εντός πεδίου" -notApplicable = "Δ/Υ" - [portal.infrastructure.audit] filterAriaLabel = "Φιλτράρισμα συμβάντων ελέγχου κατά κατηγορία" heading = "Αρχεία καταγραφής ελέγχου" @@ -7456,11 +7631,6 @@ info = "Πληροφορίες" success = "Επιτυχία" warning = "Προειδοποίηση" -[portal.infrastructure.certLabel] -certified = "Πιστοποιημένο" -inProgress = "Σε εξέλιξη" -notStarted = "Δεν έχει ξεκινήσει" - [portal.infrastructure.createKey] cancel = "Ακύρωση" createKey = "Δημιουργία key" @@ -7474,238 +7644,10 @@ subtitleCreated = "Αντιγράψτε αυτό το secret τώρα — δεν title = "Δημιουργία API key" titleCreated = "Το key δημιουργήθηκε" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "Σε ουρά" -rolledBack = "Έγινε rollback" -rolling = "Σε ανάπτυξη" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/λεπτό" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Αναπτύχθηκε από" -environment = "Περιβάλλον" -product = "Προϊόν" -status = "Κατάσταση" -version = "Έκδοση" -when = "Πότε" - -[portal.infrastructure.deployments.recent] -heading = "Πρόσφατες αναπτύξεις" -subheading = "Τα πιο πρόσφατα rollouts σε προϊόντα και περιβάλλοντα." - -[portal.infrastructure.deployments.regionColumns] -instances = "Παρουσίες" -latency = "Καθυστέρηση" -load = "Φόρτος" -p99 = "P99" -region = "Περιοχή" -status = "Κατάσταση" -throughput = "Throughput" -uptime = "Uptime" -version = "Έκδοση" - -[portal.infrastructure.deployments.regions] -heading = "Περιοχές" -subheading = "Ζωντανή υγεία για κάθε αναπτυγμένη περιοχή Stirling — καθυστέρηση, φόρτος και έκδοση rollout." - -[portal.infrastructure.deployments.regions.empty] -description = "Οι αναπτυγμένες περιοχές εμφανίζονται εδώ μόλις γίνει provision ο χώρος εργασίας σας." -title = "Δεν έχουν αναπτυχθεί περιοχές" - [portal.infrastructure.keyLabel] active = "Ενεργό" revoked = "Ανακλημένο" -[portal.infrastructure.modelLabel] -active = "Ενεργό" -degraded = "Υποβαθμισμένο" -disabled = "Απενεργοποιημένο" - -[portal.infrastructure.models] -heading = "Μοντέλα" -msValue = "{{value}} ms" -subheading = "Ο κατάλογος μοντέλων και η δρομολόγηση που τροφοδοτούν την επεξεργασία εγγράφων σε όλο τον χώρο εργασίας σας." - -[portal.infrastructure.models.byom] -description = "Καταχωρίστε ένα on-prem ή self-hosted μοντέλο και καρφιτσώστε το σε μια περιοχή για επεξεργασία με δέσμευση παραμονής δεδομένων." -title = "Φέρτε το δικό σας μοντέλο" - -[portal.infrastructure.models.catalogue] -heading = "Κατάλογος" -sub = "Managed μοντέλα διαθέσιμα στον χώρο εργασίας σας, με ζωντανή καθυστέρηση και κόστος." -subEnterprise = "Managed, bring-your-own και on-prem μοντέλα — με διαθέσιμο καρφίτσωμα ανά περιοχή." - -[portal.infrastructure.models.catalogue.empty] -description = "Τα μοντέλα στον κατάλογο του χώρου εργασίας σας εμφανίζονται εδώ." -title = "Δεν υπάρχουν διαθέσιμα μοντέλα" - -[portal.infrastructure.models.columns] -cost = "Κόστος" -latency = "Καθυστέρηση" -load = "Φόρτος" -model = "Μοντέλο" -status = "Κατάσταση" -type = "Τύπος" -version = "Έκδοση" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/κλήση" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Ενεργά μοντέλα" -avgLatency = "Μέση καθυστέρηση" -included = "Περιλαμβάνεται" -monthlySpend = "Μηνιαία δαπάνη μοντέλων" - -[portal.infrastructure.models.routing] -empty = "Δεν έχουν διαμορφωθεί κανόνες δρομολόγησης." -heading = "Κανόνες δρομολόγησης" -sub = "Ποιο μοντέλο χειρίζεται κάθε λειτουργία. Η προεπιλογή εφαρμόζεται όταν δεν ταιριάζει πιο συγκεκριμένος κανόνας." -subLocked = "Δρομολογήστε λειτουργίες σε συγκεκριμένα μοντέλα — διαθέσιμο σε πληρωμένα πλάνα." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Αναβαθμίστε σε Pro για να ελέγχετε ποιο μοντέλο χειρίζεται κάθε λειτουργία και τύπο εγγράφου." -title = "Η δρομολόγηση μοντέλων είναι πληρωμένη λειτουργία" - -[portal.infrastructure.models.routingColumns] -default = "Προεπιλογή" -docType = "Τύπος εγγράφου" -modelForAria = "Μοντέλο για {{operation}}" -operation = "Λειτουργία" -routedTo = "Δρομολογείται σε" - -[portal.infrastructure.modelTypeLabel] -classification = "Ταξινόμηση" -extraction = "Εξαγωγή" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Υποβαθμισμένη" -down = "Εκτός λειτουργίας" -healthy = "Υγιής" - -[portal.infrastructure.security.access.byok] -description = "Παρέχετε ένα key από το δικό σας KMS. Το Stirling κρυπτογραφεί με αυτό αλλά μπορεί ακόμα να διαβάζει." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Τα keys δεν φεύγουν ποτέ από το KMS σας. Το Stirling κρατά μόνο ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Το Stirling διαχειρίζεται τα encryption keys. Το απλούστερο — μηδενικές εργασίες key από την πλευρά σας." -label = "Keys που κρατούνται από το Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Πολιτική πρόσβασης εγγράφων" -subheading = "Ελέγχει ποιος μπορεί να αποκρυπτογραφήσει επεξεργασμένα έγγραφα σε κατάσταση αδράνειας." - -[portal.infrastructure.security.attestations] -heading = "Βεβαιώσεις συμμόρφωσης" -noReport = "Δεν υπάρχει διαθέσιμη αναφορά" -subheading = "Στάση ελέγχου ανά πλαίσιο, με διαθέσιμες αναφορές για βεβαιωμένους ελέγχους." -viewReport = "Προβολή αναφοράς →" - -[portal.infrastructure.security.compliance] -heading = "Συμμόρφωση" -subheading = "Βεβαιώσεις και πιστοποιήσεις που καλύπτουν την πλατφόρμα Stirling." - -[portal.infrastructure.security.empty] -description = "Η διαμόρφωση ασφαλείας του χώρου εργασίας σας θα εμφανίζεται εδώ." -title = "Η στάση ασφαλείας δεν είναι διαθέσιμη" - -[portal.infrastructure.security.hyokBanner] -description = "Με HYOK, τα encryption keys δεν φεύγουν ποτέ από το KMS σας. Το Stirling αποθηκεύει και επεξεργάζεται μόνο ciphertext που μπορείτε να ανακαλέσετε ανά πάσα στιγμή." -title = "Το Stirling δεν μπορεί να αποκρυπτογραφήσει τα έγγραφά σας" - -[portal.infrastructure.security.ipAllowlist] -empty = "Δεν έχουν διαμορφωθεί IP ranges — επιτρέπονται όλες οι IPs." -heading = "IP allowlist" -sub = "Η πρόσβαση API περιορίζεται σε αυτά τα CIDR ranges." -subLocked = "Περιορίστε την πρόσβαση API σε γνωστά IP ranges — διαθέσιμο σε πληρωμένα πλάνα." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Αναβαθμίστε σε Pro για να περιορίσετε την πρόσβαση API σε συγκεκριμένα δίκτυα." -title = "Το IP allowlisting είναι πληρωμένη λειτουργία" - -[portal.infrastructure.security.ipColumns] -added = "Προστέθηκε" -addedBy = "Προστέθηκε από" -cidr = "CIDR" -label = "Ετικέτα" - -[portal.infrastructure.security.keyManagement] -algorithm = "Αλγόριθμος" -heading = "Διαχείριση encryption keys" -keyId = "Αναγνωριστικό key" -lastRotated = "Τελευταία περιστροφή" -rotateKey = "Περιστροφή key" -rotationPolicy = "Πολιτική περιστροφής" -subheading = "Φύλαξη των keys που κρυπτογραφούν έγγραφα σε κατάσταση αδράνειας — ποιος μπορεί να αποκρυπτογραφήσει και πώς περιστρέφονται τα keys." - -[portal.infrastructure.security.managedBanner] -description = "Η φύλαξη bring-your-own-key (BYOK) και hold-your-own-key (HYOK) είναι διαθέσιμη στο Enterprise. Αναβαθμίστε για να παρέχετε keys από το δικό σας KMS." -title = "Τα keys διαχειρίζονται από το Stirling στο πλάνο σας" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Ασία-Ειρηνικός" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR data boundary" -label = "Ευρωπαϊκή Ένωση" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Ηνωμένες Πολιτείες" - -[portal.infrastructure.security.residencyHeader] -heading = "Παραμονή δεδομένων" -subheading = "Πού αποθηκεύονται και επεξεργάζονται τα έγγραφα." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} χρησιμοποιήθηκε" - -[portal.infrastructure.storage.empty] -description = "Η συνδεδεμένη αποθήκευση και η χρήση εμφανίζονται εδώ." -title = "Δεν έχει διαμορφωθεί αποθήκευση" - -[portal.infrastructure.storage.lifecycle] -active = "Ενεργό" -activeRange = "0–{{value}}d" -archived = "Αρχειοθετημένο" -coldStorage = "cold storage" -deleted = "Διαγραμμένο" -never = "ποτέ" -purged = "εκκαθαρίστηκε" - -[portal.infrastructure.storage.providers] -connect = "Σύνδεση" -connected = "Συνδεδεμένο" -heading = "Συνδεδεμένοι πάροχοι" -subheading = "Πού γράφονται τα επεξεργασμένα artifacts." - -[portal.infrastructure.storage.retention] -heading = "Διατήρηση" -subheading = "Πόσο καιρό διατηρούνται τα artifacts πριν από τη διαγραφή κύκλου ζωής." -windowLabel = "Προεπιλεγμένο παράθυρο διατήρησης" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} ημέρα" -days_other = "{{count}} ημέρες" -never = "Να μην διαγράφεται ποτέ" - -[portal.infrastructure.storage.totalUsage] -heading = "Συνολική χρήση" -progressLabel = "Χρησιμοποιημένη αποθήκευση" -subheading = "Αποθήκευση που καταναλώνεται σε όλους τους συνδεδεμένους παρόχους." - [portal.infrastructure.tabs] apiKeys = "API Keys" audit = "Αρχεία καταγραφής ελέγχου" @@ -7715,11 +7657,9 @@ security = "Ασφάλεια" storage = "Αποθήκευση" [portal.integrations] -addAnother = "Προσθήκη άλλου" availableHeading = "Διαθέσιμες" comingSoonHeading = "Προσεχώς" connect = "Σύνδεση" -connectedHeading = "Συνδεδεμένες" connectionCount_one = "{{count}} σύνδεση" connectionCount_other = "{{count}} συνδέσεις" customApi = "Προσαρμοσμένο API" @@ -7737,6 +7677,10 @@ security = "Ασφάλεια" signing = "Υπογραφή" storage = "Αποθήκευση" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Συνδεδεμένο" @@ -7767,7 +7711,6 @@ integrations = "Ενσωματώσεις" pipelines = "Ροές εργασίας" policies = "Πολιτικές" procurement = "Προμήθειες" -settings = "Ρυθμίσεις" sources = "Πηγές" usage = "Χρήση & Χρέωση" users = "Χρήστες" @@ -7784,7 +7727,7 @@ title = "Ροές εργασίας" newPipeline = "Νέα ροή εργασίας" [portal.pipelines.builder] -addStep = "Προσθήκη εργαλείου" +activate = "Activate" back = "Πίσω στις ροές εργασίας" cannotFollow = "Δεν μπορεί να δεχτεί {{produced}}" chooseAccount = "Επιλέξτε λογαριασμό" @@ -7792,28 +7735,38 @@ chooseDestination = "Επιλέξτε προορισμό" chooseOperation = "Επιλέξτε τι κάνει αυτό το βήμα" chooseSource = "Επιλέξτε πηγή" discard = "Απόρριψη αλλαγών" -enabled = "Ενεργό" inputs = "Είσοδος" inputSource = "Πηγή εισόδου" inputTrigger = "Trigger" keepEditing = "Συνέχιση επεξεργασίας" +moreActions = "More actions" needsConfiguring = "Χρειάζεται ρύθμιση" -needsUpload = "Χρειάζεται μεταφορτωμένο αρχείο" -noSources = "Δεν έχουν συνδεθεί ακόμα πηγές. Δημιουργήστε μία για να τη χρησιμοποιήσετε ως είσοδο." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Κανένα εργαλείο δεν ταιριάζει στην αναζήτησή σας." -pipelineSettings = "Ρυθμίσεις ροής εργασίας" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Αναζήτηση εργαλείων" -selectToolBody = "Προσθέστε ένα εργαλείο για να δημιουργήσετε τη ροή εργασίας σας." -selectToolTitle = "Δεν υπάρχουν εργαλεία ακόμα" sendToSystem = "Αποστολή σε άλλο σύστημα" stepsIncompatible = "Αυτά τα βήματα δεν μπορούν να εκτελεστούν σε αυτό που παράγει το προηγούμενο βήμα τους: {{tools}}." stepsNeedSetup = "Αυτά τα βήματα χρειάζονται ακόμα ρύθμιση πριν από την αποθήκευση: {{tools}}." -toolSettings = "Ρυθμίσεις εργαλείου" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Μη αναγνωρισμένη λειτουργία, διατηρήθηκε ως έχει." unsavedBody = "Έχετε μη αποθηκευμένες αλλαγές. Αποθηκεύστε τις πριν φύγετε ή απορρίψτε τις;" unsavedTitle = "Μη αποθηκευμένες αλλαγές" -uploadUnsupported = "Τα μεταφορτωμένα αρχεία δεν υποστηρίζονται ακόμα στις ροές εργασίας, επομένως αυτά τα βήματα δεν μπορούν να αποθηκευτούν: {{tools}}." usesDefaults = "Εκτελείται με προεπιλεγμένες ρυθμίσεις" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Συνδυάζει κάθε αρχείο από το προηγούμενο βήμα" @@ -7825,31 +7778,36 @@ undeclared-operation = "Δεν μπορεί να ελεγχθεί τι δέχε [portal.pipelines.composer] addTool = "Προσθήκη εργαλείου" -cancel = "Ακύρωση" -chainEmpty = "Προσθέστε ένα εργαλείο για να αρχίσετε να δημιουργείτε τη ροή εργασίας σας." create = "Δημιουργία ροής εργασίας" +createPaused = "Create paused" editingUnsupported = "Η εμφάνιση αυτών των παραμέτρων εργαλείου για επεξεργασία δεν υποστηρίζεται ακόμα." -moveDown = "Μετακίνηση κάτω" -moveUp = "Μετακίνηση πάνω" +editSource = "Edit source" name = "Όνομα" namePlaceholder = "π.χ. Redaction sweep" noToolSettings = "Αυτό το εργαλείο δεν έχει διαμορφώσιμες ρυθμίσεις." -operations_one = "Λειτουργία ({{count}})" -operations_other = "Λειτουργίες ({{count}})" output = "Έξοδος" -removeStep = "Αφαίρεση λειτουργίας" save = "Αποθήκευση αλλαγών" scheduleEvery = "Εκτέλεση κάθε" -sources = "Πηγές" -sourcesLoading = "Φόρτωση πηγών..." trigger = "Trigger" triggerManual = "Μόνο χειροκίνητα" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "ημέρες" hours = "ώρες" minutes = "λεπτά" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Διαγραφή του \"{{name}}\"; Αυτό δεν μπορεί να αναιρεθεί." cancel = "Ακύρωση" @@ -7867,6 +7825,37 @@ connectSource = "Σύνδεση πηγής" description = "Δημιουργήστε την πρώτη σας ροή εργασίας: επιλέξτε τις πηγές πάνω στις οποίες εκτελείται, συνδέστε τις λειτουργίες σε αλυσίδα και επιλέξτε πού πηγαίνει η έξοδος." title = "Δεν υπάρχουν ροές εργασίας ακόμα" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Ενεργές" paused = "Σε παύση" @@ -7879,7 +7868,6 @@ completed_one = "Η εκτέλεση ολοκληρώθηκε." completed_other = "Και οι {{count}} εκτελέσεις ολοκληρώθηκαν." empty = "Δεν υπάρχει τίποτα για εκτέλεση: οι πηγές δεν είχαν έγγραφα για επεξεργασία." failed = "Η εκτέλεση απέτυχε: {{error}}" -historyCleared = "Το ιστορικό εκκαθαρίστηκε. Η επόμενη εκτέλεση επεξεργάζεται ξανά όλα όσα βρίσκονται τώρα στις πηγές." inFlight = "Δεν υπάρχει κάτι νέο για εκτέλεση: τα έγγραφα εξακολουθούν να επεξεργάζονται από προηγούμενη εκτέλεση." parked_one = "Δεν υπάρχει τίποτα για εκτέλεση: {{count}} έγγραφο απέτυχε προηγουμένως και έχει τεθεί σε αναμονή. Διορθώστε την αιτία και έπειτα εκκαθαρίστε το ιστορικό για να το δοκιμάσετε ξανά." parked_other = "Δεν υπάρχει τίποτα για εκτέλεση: {{count}} έγγραφα απέτυχαν προηγουμένως και έχουν τεθεί σε αναμονή. Διορθώστε την αιτία και έπειτα εκκαθαρίστε το ιστορικό για να τα δοκιμάσετε ξανά." @@ -7895,6 +7883,7 @@ name = "Ροή εργασίας" sources = "Πηγές" status = "Κατάσταση" steps = "Βήματα" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Παρακολούθηση φακέλου" @@ -8209,6 +8198,24 @@ label = "Επισύναψη σε ζήτημα Jira" description = "Στέλνει το επεξεργασμένο έγγραφο ως συνημμένο." label = "Αποστολή του εγγράφου με email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Γράφει το επεξεργασμένο έγγραφο σε έναν φάκελο." label = "Μεταφόρτωση στο Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Εξαντλήθηκε" subtitle = "Η δωρεάν δοκιμή σας διαρκεί έως {{date}}. Δεν απαιτείται κάρτα." title = "Enterprise δοκιμή" -[portal.search] -ariaLabel = "Αναζήτηση" -placeholder = "Αναζήτηση στο Stirling — endpoints, pipelines, έγγραφα…" - -[portal.search.empty] -noActionsDescription = "Οι γρήγορες ενέργειες θα εμφανιστούν εδώ μόλις γίνουν διαθέσιμες." -noActionsTitle = "Δεν υπάρχουν γρήγορες ενέργειες" -noMatches = "Δεν υπάρχουν αντιστοιχίες για \"{{query}}\"" -noMatchesDescription = "Δοκιμάστε διαφορετική λέξη-κλειδί ή περιηγηθείτε στον κατάλογο." - [portal.settings.groups] admin = "Διαχειριστής" @@ -8743,10 +8740,6 @@ cancel = "Ακύρωση" confirm = "Διαγραφή" title = "Διαγραφή πηγής;" -[portal.sources.empty] -description = "Συνδέστε έναν φάκελο (και, σύντομα, cloud storage) ώστε οι πολιτικές σας να έχουν από πού να αντλούν έγγραφα." -title = "Δεν έχουν συνδεθεί ακόμη πηγές" - [portal.sources.kpi] inUse = "Σε χρήση" total = "Συνδέσεις" @@ -8785,6 +8778,7 @@ unused = "Αχρησιμοποίητο" documents = "Έγγραφα" source = "Πηγή" status = "Κατάσταση" +type = "Type" usedBy = "Πολιτικές" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Πολιτικές & Απόρρητο" [settings.preferences] title = "Προτιμήσεις" -[settings.search] -placeholder = "Αναζήτηση στις σελίδες ρυθμίσεων..." - [settings.security] description = "Ενημερώστε τον κωδικό σας για να διατηρήσετε τον λογαριασμό σας ασφαλή." title = "Ασφάλεια" @@ -10128,6 +10119,7 @@ last = "Τελευταία σελίδα" maintainRatio = "Εναλλαγή διατήρησης αναλογίας διαστάσεων" next = "Επόμενη σελίδα" noSavedSigs = "Δεν βρέθηκαν αποθηκευμένες υπογραφές" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Προσωπικές υπογραφές" previous = "Προηγούμενη σελίδα" redo = "Επανάληψη" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Αποτυχία αφαίρεσης φόντου hint = "Μεταφορτώστε εικόνα PNG ή JPG της υπογραφής σας" label = "Μεταφορτώστε εικόνα υπογραφής" placeholder = "Επιλέξτε αρχείο εικόνας" +previewAlt = "Current image signature" processing = "Επεξεργασία εικόνας..." removeBackground = "Αφαίρεση λευκού φόντου (διαφάνεια)" @@ -10179,6 +10172,17 @@ saved = "Επιλέξτε μια αποθηκευμένη υπογραφή πα text = "Αφού εισαγάγετε το όνομά σας παραπάνω, κάντε κλικ οπουδήποτε στο PDF για να τοποθετήσετε την υπογραφή σας." title = "Πώς να προσθέσετε υπογραφή" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Μετακίνηση υπογραφής" pause = "Παύση τοποθέτησης" @@ -10625,6 +10629,23 @@ title = "Μεταφόρτωση στον διακομιστή" updateButton = "Ενημέρωση στον διακομιστή" uploadButton = "Μεταφόρτωση στον διακομιστή" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Επιστροφή" pdfTools = "Εργαλεία PDF" placeholder = "Επιλέξτε ένα εργαλείο για να ξεκινήσετε" premiumFeature = "Premium λειτουργία:" -searchTools = "Αναζήτηση εργαλείων" toolsHeader = "Εργαλεία" viewAllTools = "Προβολή όλων των εργαλείων" @@ -10822,6 +10842,9 @@ removal = "Αφαίρεση" signing = "Υπογραφή" verification = "Επαλήθευση" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Δεν βρέθηκαν εργαλεία" noTools = "Δεν υπάρχουν διαθέσιμα εργαλεία" @@ -10979,6 +11002,13 @@ approver = "Εγκρίνει πολιτική" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Να ακυρωθεί η πρόσκληση προς {{email}}; Δεν θα μπορούν να συμμετάσχουν με τον τρέχοντα σύνδεσμο." cancelInviteTitle = "Ακύρωση πρόσκλησης" @@ -10998,10 +11028,8 @@ title = "Δεν υπάρχουν ακόμη μέλη" addToTeam = "Προσθήκη σε ομάδα" guestCount = "{{count}} επισκέπτης" guests = "Επισκέπτες" -guestsDesc = "Εξωτερικοί συνεργάτες, περιορισμένοι σε όσα μοιραστήκατε. Μόνο Editor." ledBy = "με επικεφαλής {{owner}}" org = "Οργανισμός" -orgDesc = "Κάτοχοι με εξουσία σε όλο τον οργανισμό και έγκριση πολιτικών" owners = "{{count}} κάτοχος" team = "Ομάδα {{name}}" teamMeta = "{{count}} άτομα" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Προσκλήθηκε από {{who}}" cancel = "Ακύρωση" -count = "{{count}} σε εκκρεμότητα" -desc = "Προσκεκλημένα άτομα που δεν έχουν συμμετάσχει ακόμη. Καταλαμβάνουν μια θέση μέχρι να αποδεχτούν." expiresInDays_one = "Λήγει σε {{count}} ημέρα" expiresInDays_other = "Λήγει σε {{count}} ημέρες" expiresToday = "Λήγει σήμερα" title = "Εκκρεμείς προσκλήσεις" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Κάτι πήγε στραβά κατά την επικοινωνία με το backend ή δεν έχετε πρόσβαση. Δοκιμάστε ξανά." title = "Δεν ήταν δυνατή η φόρτωση των μελών" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Αναζήτηση συνημμένων" title = "Συνημμένα" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Τίτλος σελιδοδείκτη" bookmarkTitleRequired = "Απαιτείται τίτλος σελιδοδείκτη" closeSidebar = "Κλείσιμο πλαϊνής γραμμής σελιδοδεικτών" collapseAll = "Σύμπτυξη όλων των σελιδοδεικτών" +empty = "No bookmarks in this document" expandAll = "Ανάπτυξη όλων των σελιδοδεικτών" searchPlaceholder = "Αναζήτηση σελιδοδεικτών" +title = "Bookmarks" [viewer.comments] addComment = "Προσθήκη σχολίου" @@ -11264,10 +11297,12 @@ locateAnnotation = "Εντοπισμός στο έγγραφο" moreActions = "Περισσότερες ενέργειες" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Σελίδα {{page}}" placingHint = "Κάντε κλικ σε μια σελίδα για τοποθέτηση… (ακύρωση)" removeCommentOnly = "Κατάργηση μόνο του σχολίου" saveReply = "Αποθήκευση απάντησης" +searchPlaceholder = "Search comments" title = "Σχόλια" typeComment = "Σχόλιο" typeInsertText = "Εισαγωγή κειμένου" @@ -11290,6 +11325,7 @@ unsavedDesc = "Έχετε μη αποθηκευμένες αλλαγές" closeSidebar = "Κλείσιμο πλαϊνής γραμμής επιπέδων" hideAll = "Απόκρυψη όλων των επιπέδων" showAll = "Εμφάνιση όλων των επιπέδων" +title = "Layers" [viewer.link] delete = "Διαγραφή συνδέσμου" @@ -11646,6 +11682,7 @@ exitRedaction = "Έξοδος από λειτουργία απόκρυψης" exportAll = "Εξαγωγή PDF" exportSelected = "Εξαγωγή επιλεγμένων σελίδων" formFill = "Συμπλήρωση φόρμας" +hideToolbar = "Hide toolbar" multiTool = "Πολυεργαλείο" panMode = "Λειτουργία μετακίνησης" print = "Εκτύπωση PDF" @@ -11666,6 +11703,7 @@ selectAll = "Επιλογή όλων" selectByNumber = "Επιλογή βάσει αριθμών σελίδων" selectLanguage = "Επιλέξτε γλώσσα" share = "Κοινή χρήση" +showToolbar = "Show toolbar" toggleAnnotations = "Εναλλαγή ορατότητας σχολιασμών" toggleAttachments = "Εναλλαγή συνημμένων" toggleBookmarks = "Εναλλαγή σελιδοδεικτών" diff --git a/frontend/editor/public/locales/en-GB/translation.toml b/frontend/editor/public/locales/en-GB/translation.toml index bdf6a739ed..3f47ffe965 100644 --- a/frontend/editor/public/locales/en-GB/translation.toml +++ b/frontend/editor/public/locales/en-GB/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alphabet" apply = "Apply" applyAndContinue = "Save & Leave" -areYouSure = "Are you sure you want to leave?" back = "Back" black = "Black" blue = "Blue" @@ -106,6 +105,7 @@ undoSuccess = "Operation undone successfully" unknown = "Unknown" unpin = "Unpin File (replace after tool run)" unsavedChanges = "You have unsaved changes to your PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Unsaved Changes" unsupported = "Unsupported" username = "Username" @@ -1879,6 +1879,9 @@ width = "Width" [app] description = "The Free Adobe Acrobat alternative (10M+ Downloads)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Convert to PDF/A-3b" convertToPdfA3bDescription = "Creates an archival PDF with embedded attachments" @@ -2933,12 +2936,16 @@ text = "To make these permissions unchangeable, use the Add Password tool to set [chat.actions] copy = "Copy message" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling agent options" +assistant = "AI Assistant" clearChat = "Clear chat" [chat.input] @@ -3006,6 +3013,7 @@ saturation = "Saturation and brightness" title = "Choose colour" [common] +actions = "Actions" back = "Back" cancel = "Cancel" close = "Close" @@ -3016,10 +3024,12 @@ continue = "Continue" copied = "Copied!" copy = "Copy" done = "Done" +enterValue = "Enter value" error = "Error" expand = "Expand" loading = "Loading..." next = "Next" +open = "Open" preview = "Preview" previous = "Previous" refresh = "Refresh" @@ -3392,6 +3402,24 @@ pdfOptions = "PDF Options" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Choose a source format first" settings = "Settings" single = "Single" @@ -3570,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Search..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Edit" @@ -3881,8 +3913,12 @@ addFiles = "Add files" addingFiles = "Adding files…" collapse = "Collapse sidebar" customizeGroups = "Customise groups" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Open files to get started" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Expand sidebar" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive is not configured" @@ -3899,9 +3935,13 @@ viewAll = "View all {{count}} files" [fileSidebar.fileItem] closeViewer = "Close viewer" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Open in viewer" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3913,6 +3953,14 @@ reset = "Reset to defaults" subtitle = "Group your files into parent categories. Add existing or new labels to a category, rename it, or create your own. Files in none of your categories appear under “Other”." title = "Sidebar categories" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Add to workspace" addToWorkspaceCount = "Add {{count}} to workspace" @@ -3954,6 +4002,7 @@ downloadAll = "Download all" downloadVersion = "Download this version" dropOverlay = "Drop files to upload" dropOverlaySub = "Files start in Local. Use 'Move to' or 'Save to cloud' to organise them into a folder." +duplicate = "Duplicate" file = "File" fileInfo = "File info" fileMenu = "File actions" @@ -4069,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Could not delete folder." deleteFolderFailedDetail = "Could not delete folder: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Could not update folder appearance." folderAppearanceFailedDetail = "Could not update folder appearance: {{message}}" moveFilesFailed = "Could not move files." @@ -4253,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "All saved" +analyzingFields = "Analysing form fields..." extractCsvError = "Failed to extract CSV" extractXlsxError = "Failed to extract XLSX" +filled = "filled" flattenAfterFilling = "Flatten after filling" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "req" requiredFieldsError = "Please fill in all required fields" rescanFields = "Re-scan fields" @@ -4807,9 +4863,6 @@ title = "Image to PDF" [imageToPdf] tags = "conversion,img,jpg,picture,photo" -[infoBanner] -dismiss = "Dismiss" - [invite] acceptError = "Failed to create account" accountFor = "Creating account for" @@ -4991,6 +5044,34 @@ uploadSuccess = "Upload Successful!" uploadSuccessMessage = "Your images have been transferred." validating = "Validating session..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Scan to upload photos. Images auto-convert to PDF." descriptionNoConvert = "Scan to upload photos from your mobile device." @@ -5009,6 +5090,14 @@ title = "Upload from Mobile" tags = "Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move,delete,migrate,divide" title = "PDF Multi Tool" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" + [oauth.error] message = "Authentication was not successful. You can close this window and try again." title = "Authentication Failed" @@ -5982,6 +6071,11 @@ header = "PDF To PDF/A" tags = "archive,long-term,standard,conversion,storage,preservation" title = "PDF To PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "print,standard,conversion,production,prepress,archive" title = "PDF To PDF/X" @@ -6293,7 +6387,6 @@ revoked = "Revoked" unnamed = "Unnamed instance" [portal.accountLink.instances.columns] -actions = "Actions" instance = "Instance" lastSeen = "Last seen" linked = "Linked" @@ -6888,6 +6981,32 @@ label = "Jira" description = "Email a document or a notification when a policy runs." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "File processed documents into Nextcloud." @@ -7190,12 +7309,11 @@ editorAction = "Editor" empty = "No documents match this filter." rowActions = "Row actions" sensitiveLabel = "Sensitive" -sensitiveTitle = "Sensitive — access required" [portal.documents.table.columns] action = "Pipeline / Action" -actions = "Actions" document = "Document" +labels = "Labels" product = "Product" status = "Status" time = "Time" @@ -7211,6 +7329,7 @@ host = "Host" lastSeen = "Last seen" region = "Region" status = "Status" +target = "Target" version = "Version" [portal.editorAdmin.health.empty] @@ -7285,6 +7404,56 @@ description = "This view hit an unexpected error. Try again, or pick another sec retry = "Try again" title = "Something went wrong on this page" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Back" body = "Download the desktop app or self-host the server." @@ -7391,11 +7560,6 @@ cancel = "Cancel" confirm = "Revoke key" title = "Revoke API key" -[portal.infrastructure.attestationLabel] -attested = "Attested" -inScope = "In scope" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filter audit events by category" heading = "Audit logs" @@ -7467,11 +7631,6 @@ info = "Info" success = "Success" warning = "Warning" -[portal.infrastructure.certLabel] -certified = "Certified" -inProgress = "In progress" -notStarted = "Not started" - [portal.infrastructure.createKey] cancel = "Cancel" createKey = "Create key" @@ -7485,240 +7644,10 @@ subtitleCreated = "Copy this secret now — it won't be shown again." title = "Create API key" titleCreated = "Key created" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "Queued" -rolledBack = "Rolled back" -rolling = "Rolling out" - -[portal.infrastructure.deployments] -loadAria = "Load for {{name}}" -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Deployed by" -environment = "Environment" -product = "Product" -status = "Status" -version = "Version" -when = "When" - -[portal.infrastructure.deployments.recent] -heading = "Recent deployments" -subheading = "The latest rollouts across products and environments." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instances" -latency = "Latency" -load = "Load" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Throughput" -uptime = "Uptime" -version = "Version" - -[portal.infrastructure.deployments.regions] -heading = "Regions" -subheading = "Live health for every deployed Stirling region — latency, load, and rollout version." - -[portal.infrastructure.deployments.regions.empty] -description = "Deployed regions appear here once your workspace is provisioned." -title = "No regions deployed" - [portal.infrastructure.keyLabel] active = "Active" revoked = "Revoked" -[portal.infrastructure.modelLabel] -active = "Active" -degraded = "Degraded" -disabled = "Disabled" - -[portal.infrastructure.models] -heading = "Models" -loadAria = "Load for {{name}}" -msValue = "{{value}} ms" -subheading = "The model catalogue and routing that powers document processing across your workspace." - -[portal.infrastructure.models.byom] -description = "Register an on-prem or self-hosted model and pin it to a region for data-residency-bound processing." -title = "Bring your own model" - -[portal.infrastructure.models.catalogue] -heading = "Catalogue" -sub = "Managed models available to your workspace, with live latency and cost." -subEnterprise = "Managed, bring-your-own, and on-prem models — with per-region pinning available." - -[portal.infrastructure.models.catalogue.empty] -description = "Models in your workspace's catalogue appear here." -title = "No models available" - -[portal.infrastructure.models.columns] -cost = "Cost" -latency = "Latency" -load = "Load" -model = "Model" -status = "Status" -type = "Type" -version = "Version" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/call" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Active models" -avgLatency = "Avg latency" -included = "Included" -monthlySpend = "Monthly model spend" - -[portal.infrastructure.models.routing] -empty = "No routing rules configured." -heading = "Routing rules" -sub = "Which model handles each operation. The default applies when no narrower rule matches." -subLocked = "Route operations to specific models — available on paid plans." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Upgrade to Pro to control which model handles each operation and document type." -title = "Model routing is a paid feature" - -[portal.infrastructure.models.routingColumns] -default = "Default" -docType = "Document type" -modelForAria = "Model for {{operation}}" -operation = "Operation" -routedTo = "Routed to" - -[portal.infrastructure.modelTypeLabel] -classification = "Classification" -extraction = "Extraction" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degraded" -down = "Down" -healthy = "Healthy" - -[portal.infrastructure.security.access.byok] -description = "Supply a key from your own KMS. Stirling encrypts with it but can still read." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Keys never leave your KMS. Stirling holds only ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling manages encryption keys. Simplest — zero key ops on your side." -label = "Stirling-held keys" - -[portal.infrastructure.security.accessPolicy] -heading = "Document access policy" -subheading = "Controls who can decrypt processed documents at rest." - -[portal.infrastructure.security.attestations] -heading = "Compliance attestations" -noReport = "No report available" -subheading = "Framework-by-framework audit posture, with reports available on attested controls." -viewReport = "View report →" - -[portal.infrastructure.security.compliance] -heading = "Compliance" -subheading = "Attestations and certifications covering the Stirling platform." - -[portal.infrastructure.security.empty] -description = "Your workspace's security configuration will appear here." -title = "Security posture unavailable" - -[portal.infrastructure.security.hyokBanner] -description = "With HYOK, encryption keys never leave your KMS. Stirling stores and processes only ciphertext you can revoke at any time." -title = "Stirling cannot decrypt your documents" - -[portal.infrastructure.security.ipAllowlist] -empty = "No IP ranges configured — all IPs allowed." -heading = "IP allowlist" -sub = "API access is restricted to these CIDR ranges." -subLocked = "Restrict API access to known IP ranges — available on paid plans." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Upgrade to Pro to restrict API access to specific networks." -title = "IP allowlisting is a paid feature" - -[portal.infrastructure.security.ipColumns] -added = "Added" -addedBy = "Added by" -cidr = "CIDR" -label = "Label" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "Encryption key management" -keyId = "Key identifier" -lastRotated = "Last rotated" -rotateKey = "Rotate key" -rotationPolicy = "Rotation policy" -subheading = "Custody of the keys that encrypt documents at rest — who can decrypt, and how keys rotate." - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key (BYOK) and hold-your-own-key (HYOK) custody are available on Enterprise. Upgrade to supply keys from your own KMS." -title = "Keys are managed by Stirling on your plan" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia Pacific" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR data boundary" -label = "European Union" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "United States" - -[portal.infrastructure.security.residencyHeader] -heading = "Data residency" -subheading = "Where documents are stored and processed." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} used" - -[portal.infrastructure.storage.empty] -description = "Connected storage and usage appear here." -title = "No storage configured" - -[portal.infrastructure.storage.lifecycle] -active = "Active" -activeRange = "0–{{value}}d" -archived = "Archived" -coldStorage = "cold storage" -deleted = "Deleted" -never = "never" -purged = "purged" - -[portal.infrastructure.storage.providers] -connect = "Connect" -connected = "Connected" -heading = "Connected providers" -subheading = "Where processed artefacts are written." - -[portal.infrastructure.storage.retention] -heading = "Retention" -subheading = "How long artefacts are kept before lifecycle deletion." -windowLabel = "Default retention window" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} day" -days_other = "{{count}} days" -never = "Never delete" - -[portal.infrastructure.storage.totalUsage] -heading = "Total usage" -progressLabel = "Storage used" -subheading = "Storage consumed across all connected providers." - [portal.infrastructure.tabs] apiKeys = "API Keys" audit = "Audit Logs" @@ -7728,11 +7657,9 @@ security = "Security" storage = "Storage" [portal.integrations] -addAnother = "Add another" availableHeading = "Available" comingSoonHeading = "Coming soon" connect = "Connect" -connectedHeading = "Connected" connectionCount_one = "{{count}} connection" connectionCount_other = "{{count}} connections" customApi = "Custom API" @@ -7750,6 +7677,10 @@ security = "Security" signing = "Signing" storage = "Storage" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Connected" @@ -7780,7 +7711,6 @@ integrations = "Integrations" pipelines = "Pipelines" policies = "Policies" procurement = "Procurement" -settings = "Settings" sources = "Sources" usage = "Usage & Billing" users = "Users" @@ -7797,7 +7727,7 @@ title = "Pipelines" newPipeline = "New pipeline" [portal.pipelines.builder] -addStep = "Add tool" +activate = "Activate" back = "Back to pipelines" cannotFollow = "Can't take {{produced}}" chooseAccount = "Choose an account" @@ -7805,28 +7735,38 @@ chooseDestination = "Choose a destination" chooseOperation = "Choose what this step does" chooseSource = "Choose a source" discard = "Discard changes" -enabled = "Enabled" inputs = "Input" inputSource = "Input source" inputTrigger = "Trigger" keepEditing = "Keep editing" +moreActions = "More actions" needsConfiguring = "Needs setting up" -needsUpload = "Needs an uploaded file" -noSources = "No sources connected yet. Create one to use it as an input." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "No tools match your search." -pipelineSettings = "Pipeline settings" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Search tools" -selectToolBody = "Add a tool to build your pipeline." -selectToolTitle = "No tools yet" sendToSystem = "Send to another system" stepsIncompatible = "These steps can't run on what their prior step produces: {{tools}}." stepsNeedSetup = "These steps still need setting up before saving: {{tools}}." -toolSettings = "Tool settings" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Unrecognised operation, kept as-is." unsavedBody = "You have unsaved changes. Save them before leaving, or discard them?" unsavedTitle = "Unsaved changes" -uploadUnsupported = "Uploaded files aren't supported in pipelines yet, so these steps can't be saved: {{tools}}." usesDefaults = "Runs with default settings" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combines every file from the previous step" @@ -7838,31 +7778,36 @@ undeclared-operation = "Can't check what this step accepts" [portal.pipelines.composer] addTool = "Add tool" -cancel = "Cancel" -chainEmpty = "Add a tool to start building your pipeline." create = "Create pipeline" +createPaused = "Create paused" editingUnsupported = "Displaying these tool params for editing is not supported yet." -moveDown = "Move down" -moveUp = "Move up" +editSource = "Edit source" name = "Name" namePlaceholder = "e.g. Redaction sweep" noToolSettings = "This tool has no configurable settings." -operations_one = "Operation ({{count}})" -operations_other = "Operations ({{count}})" output = "Output" -removeStep = "Remove operation" save = "Save changes" scheduleEvery = "Run every" -sources = "Sources" -sourcesLoading = "Loading sources..." trigger = "Trigger" triggerManual = "Manual only" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "days" hours = "hours" minutes = "minutes" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Delete \"{{name}}\"? This can't be undone." cancel = "Cancel" @@ -7880,6 +7825,37 @@ connectSource = "Connect a source" description = "Create your first pipeline: pick the sources it runs over, chain the operations, and choose where output goes." title = "No pipelines yet" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Active" paused = "Paused" @@ -7892,7 +7868,6 @@ completed_one = "Run completed." completed_other = "All {{count}} runs completed." empty = "Nothing to run: the sources had no documents to process." failed = "Run failed: {{error}}" -historyCleared = "History cleared. The next run reprocesses everything currently in the sources." inFlight = "Nothing new to run: documents are still being processed from an earlier run." parked_one = "Nothing to run: {{count}} document failed previously and is parked. Fix the cause, then clear history to retry it." parked_other = "Nothing to run: {{count}} documents failed previously and are parked. Fix the cause, then clear history to retry them." @@ -7905,10 +7880,10 @@ paused = "Paused" [portal.pipelines.table] name = "Pipeline" -open = "Open" sources = "Sources" status = "Status" steps = "Steps" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Folder watch" @@ -8223,6 +8198,24 @@ label = "Attach to a Jira issue" description = "Sends the processed document as an attachment." label = "Email the document (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Writes the processed document into a folder." label = "Upload to Nextcloud" @@ -8747,10 +8740,6 @@ cancel = "Cancel" confirm = "Delete" title = "Delete source?" -[portal.sources.empty] -description = "Connect a folder (and, soon, cloud storage) so your policies have somewhere to pull documents from." -title = "No sources connected yet" - [portal.sources.kpi] inUse = "In use" total = "Connections" @@ -8787,9 +8776,9 @@ unused = "Unused" [portal.sources.table] documents = "Documents" -open = "Open" source = "Source" status = "Status" +type = "Type" usedBy = "Policies" [portal.sources.types.box] @@ -10130,6 +10119,7 @@ last = "Last page" maintainRatio = "Toggle maintain aspect ratio" next = "Next page" noSavedSigs = "No saved signatures found" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Personal Signatures" previous = "Previous page" redo = "Redo" @@ -10168,6 +10158,7 @@ backgroundRemovalFailedTitle = "Background removal failed" hint = "Upload a PNG or JPG image of your signature" label = "Upload signature image" placeholder = "Select image file" +previewAlt = "Current image signature" processing = "Processing image..." removeBackground = "Remove white background (make transparent)" @@ -10181,6 +10172,17 @@ saved = "Select a saved signature above, then click anywhere on the PDF to place text = "After entering your name above, click anywhere on the PDF to place your signature." title = "How to add signature" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Move Signature" pause = "Pause placement" @@ -10840,6 +10842,9 @@ removal = "Removal" signing = "Signing" verification = "Verification" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "No tools found" noTools = "No tools available" @@ -10997,6 +11002,13 @@ approver = "Approves policy" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Cancel the invitation to {{email}}? They won't be able to join with the current link." cancelInviteTitle = "Cancel invitation" @@ -11016,10 +11028,8 @@ title = "No members yet" addToTeam = "Add to team" guestCount = "{{count}} guest" guests = "Guests" -guestsDesc = "External collaborators, scoped to what you shared. Editor only." ledBy = "led by {{owner}}" org = "Organisation" -orgDesc = "Owners with org-wide authority and policy approval" owners = "{{count}} owner" team = "{{name}} team" teamMeta = "{{count}} people" @@ -11059,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Invited by {{who}}" cancel = "Cancel" -count = "{{count}} pending" -desc = "Invited people who haven't joined yet. They hold a seat until they accept." expiresInDays_one = "Expires in {{count}} day" expiresInDays_other = "Expires in {{count}} days" expiresToday = "Expires today" title = "Pending invitations" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Something went wrong reaching the back end, or you don't have access. Try again." title = "Couldn't load members" @@ -11255,12 +11267,15 @@ searchPlaceholder = "Search attachments" title = "Attachments" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Bookmark title" bookmarkTitleRequired = "Bookmark title is required" closeSidebar = "Close bookmarks sidebar" collapseAll = "Collapse all bookmarks" +empty = "No bookmarks in this document" expandAll = "Expand all bookmarks" searchPlaceholder = "Search bookmarks" +title = "Bookmarks" [viewer.comments] addComment = "Add comment" @@ -11282,10 +11297,12 @@ locateAnnotation = "Locate in document" moreActions = "More actions" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Page {{page}}" placingHint = "Click a page to place… (cancel)" removeCommentOnly = "Remove comment only" saveReply = "Save reply" +searchPlaceholder = "Search comments" title = "Comments" typeComment = "Comment" typeInsertText = "Insert Text" @@ -11308,6 +11325,7 @@ unsavedDesc = "You have unsaved changes" closeSidebar = "Close layers sidebar" hideAll = "Hide all layers" showAll = "Show all layers" +title = "Layers" [viewer.link] delete = "Delete link" diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index 12ad26ceec..d2eda294b2 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alphabet" apply = "Apply" applyAndContinue = "Save & Leave" -areYouSure = "Are you sure you want to leave?" back = "Back" black = "Black" blue = "Blue" @@ -106,6 +105,7 @@ undoSuccess = "Operation undone successfully" unknown = "Unknown" unpin = "Unpin File (replace after tool run)" unsavedChanges = "You have unsaved changes to your PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Unsaved Changes" unsupported = "Unsupported" username = "Username" @@ -3402,6 +3402,24 @@ pdfOptions = "PDF Options" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Choose a source format first" settings = "Settings" single = "Single" @@ -3897,8 +3915,10 @@ collapse = "Collapse sidebar" customizeGroups = "Customize groups" dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Open files to get started" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Expand sidebar" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive is not configured" @@ -3918,8 +3938,10 @@ closeViewer = "Close viewer" dataLost = "Data lost" dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Open in viewer" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3931,6 +3953,14 @@ reset = "Show all" subtitle = "Show or hide categories in the files sidebar." title = "Sidebar categories" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Add to workspace" addToWorkspaceCount = "Add {{count}} to workspace" @@ -3939,6 +3969,7 @@ back = "Back" backToFolder = "Back to {{folder}}" backToMyFiles = "Back to My Files" breadcrumbs = "Folder path" +bulkActions = "Actions" cancel = "Cancel" classification = "Classification" clearSelection = "Clear selection" @@ -3972,6 +4003,7 @@ downloadAll = "Download all" downloadVersion = "Download this version" dropOverlay = "Drop files to upload" dropOverlaySub = "Files start in Local. Use 'Move to' or 'Save to cloud' to organize them into a folder." +duplicate = "Duplicate" file = "File" fileInfo = "File info" fileMenu = "File actions" @@ -4087,6 +4119,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Could not delete folder." deleteFolderFailedDetail = "Could not delete folder: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Could not update folder appearance." folderAppearanceFailedDetail = "Could not update folder appearance: {{message}}" moveFilesFailed = "Could not move files." @@ -4109,6 +4143,11 @@ totalSize = "Total size" type = "Type" versionHistory = "Version journey" +[filesPage.filters] +activeCount = "{{count}} filters active" +clearAll = "Clear filters" +label = "Filters" + [filesPage.folderName] cancel = "Cancel" error = "Could not save folder. Try again." @@ -4143,6 +4182,7 @@ label = "Filter files by name" placeholder = "Filter files…" [filesPage.sort] +label = "Sort files" modifiedAsc = "Oldest first" modifiedDesc = "Recent first" nameAsc = "Name A→Z" @@ -6038,6 +6078,11 @@ header = "PDF To PDF/A" tags = "archive,long-term,standard,conversion,storage,preservation" title = "PDF To PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "print,standard,conversion,production,prepress,archive" title = "PDF To PDF/X" @@ -7705,7 +7750,6 @@ moreActions = "More actions" needsConfiguring = "Needs setting up" needsDestination = "No destination chosen" needsSource = "No source chosen" -needsUpload = "Needs an uploaded file" noToolMatches = "No tools match your search." pause = "Pause" rename = "Rename pipeline" @@ -7713,11 +7757,11 @@ searchTools = "Search tools" sendToSystem = "Send to another system" stepsIncompatible = "These steps can't run on what their prior step produces: {{tools}}." stepsNeedSetup = "These steps still need setting up before saving: {{tools}}." +supportingFiles = "Supporting files" testRun = "Test with a file" unknownStep = "Unrecognized operation, kept as-is." unsavedBody = "You have unsaved changes. Save them before leaving, or discard them?" unsavedTitle = "Unsaved changes" -uploadUnsupported = "Uploaded files aren't supported in pipelines yet, so these steps can't be saved: {{tools}}." usesDefaults = "Runs with default settings" viewDefinition = "View definition" @@ -7730,7 +7774,6 @@ saveHeading = "To save your changes:" schedule = "Set how often it runs" setup = "Finish setting up: {{tools}}" source = "Choose an input source" -upload = "Remove steps that need an uploaded file: {{tools}}" [portal.pipelines.builder.diagnostic] fan-in = "Combines every incoming file" @@ -9660,7 +9703,9 @@ toolNotAvailableLocally = "Your Stirling-PDF server is offline and \"{{endpoint} expired = "Your session has expired. Please refresh the page and try again." [settings] +backToSections = "All settings" close = "Close" +title = "Settings" [settings.ai] documents = "Documents & RAG" @@ -10599,6 +10644,7 @@ ariaLabel = "Super search" filtersAriaLabel = "Search filters" hint = "Type to search" placeholder = "Search Stirling" +placeholderShort = "Search" showLess = "Show less" showMore = "Show {{count}} more" @@ -11299,8 +11345,11 @@ columnDefault = "Column {{index}}" convertToPdf = "Convert to PDF" csvStats = "{{rows}} rows · {{columns}} columns · {{size}}" emptyFile = "Empty file" +htmlHidePreview = "Hide preview" htmlPreview = "HTML preview" +htmlPreviewMobileHidden = "HTML pages are laid out for desktop widths, so the preview is off by default here." htmlPreviewWarning = "HTML preview - external resources may not load · {{size}}" +htmlShowPreview = "Show preview anyway" invalidJson = "Invalid JSON - showing raw content" lineNumbers = "Line numbers" loading = "Loading..." @@ -11647,6 +11696,7 @@ exportAll = "Export PDF" exportSelected = "Export Selected Pages" formFill = "Fill Form" hideToolbar = "Hide toolbar" +moreActions = "More actions" multiTool = "Multi-Tool" panMode = "Pan Mode" print = "Print PDF" @@ -11667,6 +11717,8 @@ selectAll = "Select All" selectByNumber = "Select by Page Numbers" selectLanguage = "Select language" share = "Share" +showAllTools = "Show all tools" +showFewerTools = "Collapse toolbar" showToolbar = "Show toolbar" toggleAnnotations = "Toggle Annotations Visibility" toggleAttachments = "Toggle Attachments" diff --git a/frontend/editor/public/locales/es-ES/translation.toml b/frontend/editor/public/locales/es-ES/translation.toml index 68f0cb4ecc..bc1300bceb 100644 --- a/frontend/editor/public/locales/es-ES/translation.toml +++ b/frontend/editor/public/locales/es-ES/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabeto" apply = "Aplicar" applyAndContinue = "Guardar y salir" -areYouSure = "¿Seguro que quieres salir?" back = "Atrás" black = "Negro" blue = "Azul" @@ -106,6 +105,7 @@ undoSuccess = "Operación deshecha correctamente" unknown = "Desconocido" unpin = "Desfijar archivo (reemplazar tras ejecutar la herramienta)" unsavedChanges = "Tienes cambios sin guardar en tu PDF. ¿Qué te gustaría hacer?" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Cambios sin guardar" unsupported = "No compatible" username = "Nombre de usuario" @@ -1879,6 +1879,9 @@ width = "Ancho" [app] description = "La alternativa gratuita a Adobe Acrobat (más de 10 millones de descargas)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Convertir a PDF/A-3b" convertToPdfA3bDescription = "Crea un PDF para archivo con adjuntos incrustados" @@ -2933,12 +2936,16 @@ text = "Para hacer estos permisos inmutables, usa la herramienta Agregar Contras [chat.actions] copy = "Copiar mensaje" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opciones del agente Stirling" +assistant = "AI Assistant" clearChat = "Borrar chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Esta operación usará tus créditos en la nube" tooltip = "Tomar color de la pantalla" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Elija el color" [common] +actions = "Actions" back = "Atrás" cancel = "Cancelar" close = "Cerrar" +codeSample = "Code sample" collapse = "Contraer" confirm = "Confirmar" continue = "Continuar" copied = "¡Copiado!" copy = "Copiar" done = "Hecho" +enterValue = "Enter value" error = "Error" expand = "Expandir" loading = "Cargando..." next = "Siguiente" +open = "Open" preview = "Vista previa" previous = "Anterior" refresh = "Actualizar" remaining = "Restante" retry = "Reintentar" save = "Guardar" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Borrar selección" @@ -3185,6 +3199,7 @@ title = "Método de Compresión" [compress.settings] desiredSize = "Tamaño de archivo deseado" desiredSizePlaceholder = "Introduce el tamaño" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "La compresión es una forma fácil de reducir el tamaño de su archivo. Elija Tamaño de Archivo para ingresar un tamaño objetivo y nosotros ajustaremos la calidad por usted. Elija Calidad para establecer la intensidad de compresión manualmente." @@ -3387,6 +3402,24 @@ pdfOptions = "Opciones de PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Seleccione primero un formato de origen" settings = "Configuración" single = "Individual" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Buscar..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Editar" @@ -3824,11 +3861,13 @@ shareSelected = "Compartir seleccionados" sharing = "Uso compartido" showAll = "Mostrar todo" showHistory = "Mostrar Historial" +sortBy = "Sort files" sortByDate = "Ordenar por fecha" sortByName = "Ordenar por nombre" sortBySize = "Ordenar por tamaño" storage = "Almacenamiento" storageState = "Almacenamiento" +storageUsed = "Storage used" synced = "Sincronizado" title = "Cargar Archivos PDF" toolChain = "Herramientas Aplicadas" @@ -3874,8 +3913,12 @@ addFiles = "Añadir archivos" addingFiles = "Añadiendo archivos…" collapse = "Contraer barra lateral" customizeGroups = "Personalizar grupos" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Abre archivos para empezar" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Expandir barra lateral" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive no está configurado" @@ -3888,15 +3931,17 @@ openFromComputer = "Abrir desde el ordenador" openSettings = "Abrir configuración" other = "Otros" recent = "Recientes" -search = "Buscar" -searchPlaceholder = "Buscar archivos..." viewAll = "Ver los {{count}} archivos" [fileSidebar.fileItem] closeViewer = "Cerrar visor" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Abrir en el visor" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Restablecer valores predeterminados" subtitle = "Agrupa tus archivos en categorías principales. Añade etiquetas existentes o nuevas a una categoría, cámbiale el nombre o crea la tuya propia. Los archivos que no estén en ninguna de tus categorías aparecen en “Otros”." title = "Categorías de la barra lateral" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Añadir al espacio de trabajo" addToWorkspaceCount = "Añadir {{count}} al espacio de trabajo" @@ -3918,7 +3971,6 @@ backToMyFiles = "Volver a Mis archivos" breadcrumbs = "Ruta de carpeta" cancel = "Cancelar" classification = "Clasificación" -clearSearch = "Borrar búsqueda" clearSelection = "Borrar selección" closeDetails = "Cerrar detalles" create = "Crear" @@ -3950,6 +4002,7 @@ downloadAll = "Descargar todo" downloadVersion = "Descargar esta versión" dropOverlay = "Suelta archivos para subirlos" dropOverlaySub = "Los archivos empiezan en Local. Usa 'Mover a' o 'Guardar en la nube' para organizarlos en una carpeta." +duplicate = "Duplicate" file = "Archivo" fileInfo = "File info" fileMenu = "Acciones de archivo" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Redimensionar árbol de carpetas (teclas de flecha, Mayús p save = "Guardar" saveToServer = "Guardar en el servidor" saveToServerDisabledHint = "Guardar en el servidor no está habilitado en este servidor. Pide a tu administrador que lo habilite." -search = "Buscar" -searchPlaceholder = "Buscar en esta carpeta y subcarpetas" selectAll = "Seleccionar todo" selectAllHint = "Haz clic para seleccionar todo. Consejo: mantén pulsado Ctrl (o Cmd) para añadir archivos uno a uno, Mayús para seleccionar un rango." selectedCount = "{{count}} seleccionados" @@ -4044,6 +4095,10 @@ title = "Aún no hay archivos en la nube" hint = "Los archivos guardados sin subir se quedan aquí. Suelta un archivo para añadir uno." title = "No hay archivos solo locales" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Los archivos que abras o edites aparecerán aquí." title = "Nada modificado aún" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "No se pudo eliminar la carpeta." deleteFolderFailedDetail = "No se pudo eliminar la carpeta: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "No se pudo actualizar la apariencia de la carpeta." folderAppearanceFailedDetail = "No se pudo actualizar la apariencia de la carpeta: {{message}}" moveFilesFailed = "No se pudieron mover los archivos." @@ -4113,6 +4170,11 @@ localHint = "Solo almacenado en este navegador" shared = "Compartido" sharedHint = "Compartido contigo mediante enlace" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Más antiguos primero" modifiedDesc = "Más recientes primero" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "No hay archivos disponibles en el almacenamiento. Abra a open = "Abrir" openFile = "Abrir archivo" openFiles = "Abrir archivos" +selectFile = "Select {{name}}" selectFromStorage = "Seleccionar del Almacenamiento" upload = "Cargar" uploadFile = "Cargar Archivo" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Todo guardado" +analyzingFields = "Analysing form fields..." extractCsvError = "No se pudo extraer CSV" extractXlsxError = "No se pudo extraer XLSX" +filled = "filled" flattenAfterFilling = "Aplanar después de rellenar" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "oblig." requiredFieldsError = "Rellena todos los campos obligatorios" rescanFields = "Volver a escanear campos" @@ -4795,9 +4863,6 @@ title = "Imagen a PDF" [imageToPdf] tags = "conversión,img,jpg,imagen,fotografía" -[infoBanner] -dismiss = "Descartar" - [invite] acceptError = "No se pudo crear la cuenta" accountFor = "Creando cuenta para" @@ -4979,6 +5044,34 @@ uploadSuccess = "¡Subida correcta!" uploadSuccessMessage = "Tus imágenes han sido transferidas." validating = "Validando la sesión..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Escanea para subir fotos. Las imágenes se convierten automáticamente a PDF." descriptionNoConvert = "Escanea para subir fotos desde tu dispositivo móvil." @@ -4997,8 +5090,13 @@ title = "Subir desde el móvil" tags = "Multi-herramienta,Multi-operación,Interfaz de usuario,Arrastrar con un click,front end,lado del cliente" title = "Multi-herramienta PDF" -[navbar] -search = "Buscar" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "La autenticación no se realizó correctamente. Puede cerrar esta ventana e intentarlo de nuevo." @@ -5127,6 +5225,7 @@ activeFiles = "La vista de Archivos activos muestra todos los P allTools = "Este es el panel de Todas las herramientas, donde puedes buscar y seleccionar entre todas las herramientas PDF disponibles." close = "Cerrar" cropSettings = "Ahora que hemos seleccionado el archivo que queremos recortar, podemos configurar la herramienta Recortar para elegir el área a la que queremos recortar el PDF." +dialogLabel = "Onboarding" fileCheckbox = "Al hacer clic en uno de los archivos lo seleccionas para procesarlo. Puedes seleccionar varios archivos para operaciones por lotes." fileReplacement = "El archivo modificado reemplazará automáticamente al original en el Workbench, lo que te permitirá pasarlo fácilmente por más herramientas." filesButton = "El botón Archivos en la barra de acceso rápido te permite subir PDFs sobre los que usar las herramientas." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF a PDF/A" tags = "archivo,largo plazo,estándar,conversión,almacenamiento,conservación" title = "PDF a PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "impresión,estándar,conversión,producción,preimpresión,archivo" title = "PDF a PDF/X" @@ -6045,6 +6151,7 @@ small = "500 Créditos" xsmall = "100 Créditos" [plan.availablePlans] +currency = "Billing currency" subtitle = "Elige el plan que se ajuste a tus necesidades" title = "Planes disponibles" @@ -6624,6 +6731,7 @@ reachedTitle = "Límite de gasto mensual alcanzado" title = "No se ha podido abrir el portal de Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "de {{allowance}} PDFs gratuitos usados" capSuffix_other = "de {{allowance}} PDFs gratuitos usados" eyebrow = "Prueba de Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Envía por email un documento o una notificación cuando se ejecute una política." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Archiva documentos procesados en Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDKs oficiales" beta = "Beta" deprecated = "Obsoleto" -[portal.docs.search] -empty = "No hay documentación coincidente" -placeholder = "Buscar en documentación" -results = "{{count}} resultados" - [portal.docs.skills] eyebrow = "HABILIDADES" lead = "Capacidades incluidas y con nombre que tu agente invoca como una sola herramienta. Cada habilidad es una cadena de operaciones determinista con evaluaciones adjuntas." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Ningún documento coincide con este filtro." rowActions = "Acciones de fila" sensitiveLabel = "Sensible" -sensitiveTitle = "Sensible — se requiere acceso" [portal.documents.table.columns] action = "Pipeline / Acción" document = "Documento" +labels = "Labels" product = "Producto" status = "Estado" time = "Hora" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Última vez visto" region = "Región" status = "Estado" +target = "Target" version = "Versión" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Esta vista ha encontrado un error inesperado. Inténtalo de nuevo retry = "Intentarlo de nuevo" title = "Algo ha salido mal en esta página" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Atrás" body = "Descarga la aplicación de escritorio o autoaloja el servidor." @@ -7380,11 +7560,6 @@ cancel = "Cancelar" confirm = "Revocar clave" title = "Revocar clave de API" -[portal.infrastructure.attestationLabel] -attested = "Atestiguado" -inScope = "Dentro del alcance" -notApplicable = "N/D" - [portal.infrastructure.audit] filterAriaLabel = "Filtrar eventos de auditoría por categoría" heading = "Registros de auditoría" @@ -7456,11 +7631,6 @@ info = "Información" success = "Correcto" warning = "Advertencia" -[portal.infrastructure.certLabel] -certified = "Certificado" -inProgress = "En curso" -notStarted = "No iniciado" - [portal.infrastructure.createKey] cancel = "Cancelar" createKey = "Crear clave" @@ -7474,238 +7644,10 @@ subtitleCreated = "Copia este secreto ahora — no se volverá a mostrar." title = "Crear clave API" titleCreated = "Clave creada" -[portal.infrastructure.deployLabel] -live = "En vivo" -queued = "En cola" -rolledBack = "Revertido" -rolling = "Desplegando" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Desplegado por" -environment = "Entorno" -product = "Producto" -status = "Estado" -version = "Versión" -when = "Cuándo" - -[portal.infrastructure.deployments.recent] -heading = "Despliegues recientes" -subheading = "Los últimos despliegues en productos y entornos." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instancias" -latency = "Latencia" -load = "Carga" -p99 = "P99" -region = "Región" -status = "Estado" -throughput = "Rendimiento" -uptime = "Tiempo de actividad" -version = "Versión" - -[portal.infrastructure.deployments.regions] -heading = "Regiones" -subheading = "Estado en vivo de cada región Stirling desplegada — latencia, carga y versión de despliegue." - -[portal.infrastructure.deployments.regions.empty] -description = "Las regiones desplegadas aparecerán aquí cuando se aprovisione tu espacio de trabajo." -title = "No hay regiones desplegadas" - [portal.infrastructure.keyLabel] active = "Activa" revoked = "Revocada" -[portal.infrastructure.modelLabel] -active = "Activo" -degraded = "Degradado" -disabled = "Deshabilitado" - -[portal.infrastructure.models] -heading = "Modelos" -msValue = "{{value}} ms" -subheading = "El catálogo de modelos y el enrutamiento que impulsa el procesamiento de documentos en todo tu espacio de trabajo." - -[portal.infrastructure.models.byom] -description = "Registra un modelo on-prem o autoalojado y fíjalo a una región para procesamiento sujeto a residencia de datos." -title = "Trae tu propio modelo" - -[portal.infrastructure.models.catalogue] -heading = "Catálogo" -sub = "Modelos gestionados disponibles para tu espacio de trabajo, con latencia y coste en vivo." -subEnterprise = "Modelos gestionados, propios y on-prem — con fijación por región disponible." - -[portal.infrastructure.models.catalogue.empty] -description = "Los modelos del catálogo de tu espacio de trabajo aparecen aquí." -title = "No hay modelos disponibles" - -[portal.infrastructure.models.columns] -cost = "Coste" -latency = "Latencia" -load = "Carga" -model = "Modelo" -status = "Estado" -type = "Tipo" -version = "Versión" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/llamada" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Modelos activos" -avgLatency = "Latencia media" -included = "Incluido" -monthlySpend = "Gasto mensual en modelos" - -[portal.infrastructure.models.routing] -empty = "No hay reglas de enrutamiento configuradas." -heading = "Reglas de enrutamiento" -sub = "Qué modelo gestiona cada operación. El valor predeterminado se aplica cuando no coincide ninguna regla más específica." -subLocked = "Dirige operaciones a modelos específicos — disponible en planes de pago." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Mejora a Pro para controlar qué modelo gestiona cada operación y tipo de documento." -title = "El enrutamiento de modelos es una función de pago" - -[portal.infrastructure.models.routingColumns] -default = "Predeterminado" -docType = "Tipo de documento" -modelForAria = "Modelo para {{operation}}" -operation = "Operación" -routedTo = "Enrutado a" - -[portal.infrastructure.modelTypeLabel] -classification = "Clasificación" -extraction = "Extracción" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degradado" -down = "Caído" -healthy = "Correcto" - -[portal.infrastructure.security.access.byok] -description = "Proporciona una clave de tu propio KMS. Stirling cifra con ella, pero aún puede leer." -label = "Trae tu propia clave (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Las claves nunca salen de tu KMS. Stirling solo conserva texto cifrado." -label = "Conserva tu propia clave (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling gestiona las claves de cifrado. Lo más sencillo — cero operaciones de claves por tu parte." -label = "Claves conservadas por Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Política de acceso a documentos" -subheading = "Controla quién puede descifrar documentos procesados en reposo." - -[portal.infrastructure.security.attestations] -heading = "Atestaciones de cumplimiento" -noReport = "No hay informe disponible" -subheading = "Postura de auditoría marco por marco, con informes disponibles sobre controles atestados." -viewReport = "Ver informe →" - -[portal.infrastructure.security.compliance] -heading = "Cumplimiento" -subheading = "Atestaciones y certificaciones que cubren la plataforma Stirling." - -[portal.infrastructure.security.empty] -description = "La configuración de seguridad de tu espacio de trabajo aparecerá aquí." -title = "Postura de seguridad no disponible" - -[portal.infrastructure.security.hyokBanner] -description = "Con HYOK, las claves de cifrado nunca salen de tu KMS. Stirling almacena y procesa solo texto cifrado que puedes revocar en cualquier momento." -title = "Stirling no puede descifrar tus documentos" - -[portal.infrastructure.security.ipAllowlist] -empty = "No hay rangos IP configurados — todas las IP están permitidas." -heading = "Lista de IP permitidas" -sub = "El acceso a API está restringido a estos rangos CIDR." -subLocked = "Restringe el acceso a API a rangos IP conocidos — disponible en planes de pago." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Mejora a Pro para restringir el acceso a API a redes específicas." -title = "La lista de IP permitidas es una función de pago" - -[portal.infrastructure.security.ipColumns] -added = "Añadido" -addedBy = "Añadido por" -cidr = "CIDR" -label = "Etiqueta" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritmo" -heading = "Gestión de claves de cifrado" -keyId = "Identificador de clave" -lastRotated = "Última rotación" -rotateKey = "Rotar clave" -rotationPolicy = "Política de rotación" -subheading = "Custodia de las claves que cifran documentos en reposo — quién puede descifrar y cómo rotan las claves." - -[portal.infrastructure.security.managedBanner] -description = "La custodia trae-tu-propia-clave (BYOK) y conserva-tu-propia-clave (HYOK) está disponible en Enterprise. Mejora para proporcionar claves desde tu propio KMS." -title = "Las claves las gestiona Stirling en tu plan" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia-Pacífico" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · límite de datos GDPR" -label = "Unión Europea" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Estados Unidos" - -[portal.infrastructure.security.residencyHeader] -heading = "Residencia de datos" -subheading = "Dónde se almacenan y procesan los documentos." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} usado" - -[portal.infrastructure.storage.empty] -description = "El almacenamiento conectado y el uso aparecerán aquí." -title = "No hay almacenamiento configurado" - -[portal.infrastructure.storage.lifecycle] -active = "Activo" -activeRange = "0–{{value}} d" -archived = "Archivado" -coldStorage = "almacenamiento en frío" -deleted = "Eliminado" -never = "nunca" -purged = "purgado" - -[portal.infrastructure.storage.providers] -connect = "Conectar" -connected = "Conectado" -heading = "Proveedores conectados" -subheading = "Dónde se escriben los artefactos procesados." - -[portal.infrastructure.storage.retention] -heading = "Retención" -subheading = "Cuánto tiempo se conservan los artefactos antes de la eliminación del ciclo de vida." -windowLabel = "Ventana de retención predeterminada" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} día" -days_other = "{{count}} días" -never = "No eliminar nunca" - -[portal.infrastructure.storage.totalUsage] -heading = "Uso total" -progressLabel = "Almacenamiento usado" -subheading = "Almacenamiento consumido en todos los proveedores conectados." - [portal.infrastructure.tabs] apiKeys = "Claves API" audit = "Registros de auditoría" @@ -7715,11 +7657,9 @@ security = "Seguridad" storage = "Almacenamiento" [portal.integrations] -addAnother = "Añadir otra" availableHeading = "Disponibles" comingSoonHeading = "Próximamente" connect = "Conectar" -connectedHeading = "Conectadas" connectionCount_one = "{{count}} conexión" connectionCount_other = "{{count}} conexiones" customApi = "API personalizada" @@ -7737,6 +7677,10 @@ security = "Seguridad" signing = "Firma" storage = "Almacenamiento" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Conectada" @@ -7767,7 +7711,6 @@ integrations = "Integraciones" pipelines = "Pipelines" policies = "Políticas" procurement = "Compras" -settings = "Configuración" sources = "Orígenes" usage = "Uso y facturación" users = "Usuarios" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Nuevo pipeline" [portal.pipelines.builder] -addStep = "Añadir herramienta" +activate = "Activate" back = "Volver a pipelines" cannotFollow = "No puede tomar {{produced}}" chooseAccount = "Elige una cuenta" @@ -7792,28 +7735,38 @@ chooseDestination = "Elige un destino" chooseOperation = "Elige qué hace este paso" chooseSource = "Elige un origen" discard = "Descartar cambios" -enabled = "Activado" inputs = "Entrada" inputSource = "Origen de entrada" inputTrigger = "Disparador" keepEditing = "Seguir editando" +moreActions = "More actions" needsConfiguring = "Necesita configuración" -needsUpload = "Necesita un archivo subido" -noSources = "Aún no hay orígenes conectados. Crea uno para usarlo como entrada." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Ninguna herramienta coincide con tu búsqueda." -pipelineSettings = "Configuración del pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Buscar herramientas" -selectToolBody = "Añade una herramienta para crear tu pipeline." -selectToolTitle = "Aún no hay herramientas" sendToSystem = "Enviar a otro sistema" stepsIncompatible = "Estos pasos no se pueden ejecutar sobre lo que produce su paso anterior: {{tools}}." stepsNeedSetup = "Estos pasos aún necesitan configuración antes de guardar: {{tools}}." -toolSettings = "Configuración de la herramienta" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Operación no reconocida, conservada tal cual." unsavedBody = "Tienes cambios sin guardar. ¿Guardarlos antes de salir o descartarlos?" unsavedTitle = "Cambios sin guardar" -uploadUnsupported = "Los archivos subidos aún no son compatibles con pipelines, por lo que estos pasos no se pueden guardar: {{tools}}." usesDefaults = "Se ejecuta con la configuración predeterminada" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combina todos los archivos del paso anterior" @@ -7825,31 +7778,36 @@ undeclared-operation = "No se puede comprobar qué acepta este paso" [portal.pipelines.composer] addTool = "Añadir herramienta" -cancel = "Cancelar" -chainEmpty = "Añade una herramienta para empezar a crear tu pipeline." create = "Crear pipeline" +createPaused = "Create paused" editingUnsupported = "Aún no se admite mostrar estos parámetros de herramienta para editarlos." -moveDown = "Mover abajo" -moveUp = "Mover arriba" +editSource = "Edit source" name = "Nombre" namePlaceholder = "p. ej. Barrido de censura" noToolSettings = "Esta herramienta no tiene ajustes configurables." -operations_one = "Operación ({{count}})" -operations_other = "Operaciones ({{count}})" output = "Salida" -removeStep = "Eliminar operación" save = "Guardar cambios" scheduleEvery = "Ejecutar cada" -sources = "Orígenes" -sourcesLoading = "Cargando orígenes..." trigger = "Desencadenador" triggerManual = "Solo manual" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "días" hours = "horas" minutes = "minutos" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "¿Eliminar \"{{name}}\"? Esta acción no se puede deshacer." cancel = "Cancelar" @@ -7867,6 +7825,37 @@ connectSource = "Conectar un origen" description = "Crea tu primer pipeline: elige los orígenes sobre los que se ejecuta, encadena las operaciones y elige dónde va la salida." title = "Aún no hay pipelines" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Activos" paused = "Pausados" @@ -7879,7 +7868,6 @@ completed_one = "Ejecución completada." completed_other = "Las {{count}} ejecuciones se han completado." empty = "Nada que ejecutar: los orígenes no tenían documentos que procesar." failed = "La ejecución ha fallado: {{error}}" -historyCleared = "Historial borrado. La próxima ejecución vuelve a procesar todo lo que haya actualmente en los orígenes." inFlight = "Nada nuevo que ejecutar: todavía se están procesando documentos de una ejecución anterior." parked_one = "Nada que ejecutar: {{count}} documento falló anteriormente y está aparcado. Corrige la causa y luego borra el historial para reintentarlo." parked_other = "Nada que ejecutar: {{count}} documentos fallaron anteriormente y están aparcados. Corrige la causa y luego borra el historial para reintentarlos." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Orígenes" status = "Estado" steps = "Pasos" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Supervisión de carpeta" @@ -8209,6 +8198,24 @@ label = "Adjuntar a una incidencia de Jira" description = "Envía el documento procesado como adjunto." label = "Enviar el documento por correo electrónico (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Escribe el documento procesado en una carpeta." label = "Subir a Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Agotadas" subtitle = "Tu prueba gratuita se extiende hasta {{date}}. No se requiere tarjeta." title = "Prueba Enterprise" -[portal.search] -ariaLabel = "Buscar" -placeholder = "Buscar en Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Las acciones rápidas aparecerán aquí cuando estén disponibles." -noActionsTitle = "Sin acciones rápidas" -noMatches = "No hay coincidencias para \"{{query}}\"" -noMatchesDescription = "Prueba con otra palabra clave o explora el catálogo." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Cancelar" confirm = "Eliminar" title = "¿Eliminar fuente?" -[portal.sources.empty] -description = "Conecta una carpeta (y, pronto, almacenamiento en la nube) para que tus políticas tengan de dónde extraer documentos." -title = "Aún no hay fuentes conectadas" - [portal.sources.kpi] inUse = "En uso" total = "Conexiones" @@ -8785,6 +8778,7 @@ unused = "Sin usar" documents = "Documentos" source = "Fuente" status = "Estado" +type = "Type" usedBy = "Políticas" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Políticas y privacidad" [settings.preferences] title = "Preferencias" -[settings.search] -placeholder = "Buscar en las páginas de ajustes..." - [settings.security] description = "Actualiza tu contraseña para mantener tu cuenta segura." title = "Seguridad" @@ -10128,6 +10119,7 @@ last = "Última página" maintainRatio = "Activar/desactivar la relación de aspecto" next = "Siguiente página" noSavedSigs = "No se encontraron firmas guardadas" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Firmas Personales" previous = "Página anterior" redo = "Rehacer" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Error al eliminar el fondo" hint = "Cargue una imagen PNG o JPG de su firma" label = "Cargar imagen de firma" placeholder = "Seleccionar archivo de imagen" +previewAlt = "Current image signature" processing = "Procesando imagen..." removeBackground = "Eliminar fondo blanco (hacer transparente)" @@ -10179,6 +10172,17 @@ saved = "Seleccione una firma guardada arriba y haga clic en cualquier parte del text = "Tras introducir tu nombre arriba, haz clic en cualquier parte del PDF para colocar tu firma." title = "Cómo añadir firma" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Mover firma" pause = "Pausar colocación" @@ -10625,6 +10629,23 @@ title = "Subir al servidor" updateButton = "Actualizar en el servidor" uploadButton = "Subir al servidor" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Volver" pdfTools = "Herramientas PDF" placeholder = "Elige una herramienta para empezar" premiumFeature = "Función Premium:" -searchTools = "Buscar herramientas" toolsHeader = "Herramientas" viewAllTools = "Ver todas las herramientas" @@ -10822,6 +10842,9 @@ removal = "Eliminación" signing = "Firma" verification = "Verificación" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "No se encontraron herramientas" noTools = "No hay herramientas disponibles" @@ -10979,6 +11002,13 @@ approver = "Aprueba política" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "¿Cancelar la invitación a {{email}}? No podrá unirse con el enlace actual." cancelInviteTitle = "Cancelar invitación" @@ -10998,10 +11028,8 @@ title = "Aún no hay miembros" addToTeam = "Añadir al equipo" guestCount = "{{count}} invitado" guests = "Invitados" -guestsDesc = "Colaboradores externos, limitados a lo que hayas compartido. Solo Editor." ledBy = "dirigido por {{owner}}" org = "Organización" -orgDesc = "Propietarios con autoridad en toda la org y aprobación de políticas" owners = "{{count}} propietario" team = "Equipo {{name}}" teamMeta = "{{count}} personas" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Invitado por {{who}}" cancel = "Cancelar" -count = "{{count}} pendientes" -desc = "Personas invitadas que aún no se han unido. Ocupan una plaza hasta que acepten." expiresInDays_one = "Caduca en {{count}} día" expiresInDays_other = "Caduca en {{count}} días" expiresToday = "Caduca hoy" title = "Invitaciones pendientes" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Algo ha fallado al contactar con el backend, o no tienes acceso. Inténtalo de nuevo." title = "No se han podido cargar los miembros" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Buscar adjuntos" title = "Adjuntos" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Título del marcador" bookmarkTitleRequired = "El título del marcador es obligatorio" closeSidebar = "Cerrar barra lateral de marcadores" collapseAll = "Contraer todos los marcadores" +empty = "No bookmarks in this document" expandAll = "Expandir todos los marcadores" searchPlaceholder = "Buscar marcadores" +title = "Bookmarks" [viewer.comments] addComment = "Añadir comentario" @@ -11264,10 +11297,12 @@ locateAnnotation = "Localizar en el documento" moreActions = "Más acciones" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Página {{page}}" placingHint = "Haz clic en una página para colocar… (cancelar)" removeCommentOnly = "Quitar solo el comentario" saveReply = "Guardar respuesta" +searchPlaceholder = "Search comments" title = "Comentarios" typeComment = "Comentario" typeInsertText = "Insertar texto" @@ -11290,6 +11325,7 @@ unsavedDesc = "Tienes cambios sin guardar" closeSidebar = "Cerrar barra lateral de capas" hideAll = "Ocultar todas las capas" showAll = "Mostrar todas las capas" +title = "Layers" [viewer.link] delete = "Eliminar enlace" @@ -11646,6 +11682,7 @@ exitRedaction = "Salir del modo de redacción" exportAll = "Exportar PDF" exportSelected = "Exportar páginas seleccionadas" formFill = "Rellenar formulario" +hideToolbar = "Hide toolbar" multiTool = "Multiherramienta" panMode = "Modo desplazar" print = "Imprimir PDF" @@ -11666,6 +11703,7 @@ selectAll = "Seleccionar todo" selectByNumber = "Seleccionar por números de página" selectLanguage = "Seleccionar idioma" share = "Compartir" +showToolbar = "Show toolbar" toggleAnnotations = "Alternar visibilidad de las anotaciones" toggleAttachments = "Alternar adjuntos" toggleBookmarks = "Alternar marcadores" diff --git a/frontend/editor/public/locales/eu-ES/translation.toml b/frontend/editor/public/locales/eu-ES/translation.toml index e951d9413d..4b41940aae 100644 --- a/frontend/editor/public/locales/eu-ES/translation.toml +++ b/frontend/editor/public/locales/eu-ES/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabetoa" apply = "Aplikatu" applyAndContinue = "Gorde eta irten" -areYouSure = "Ziur zaude irten nahi duzula?" back = "Atzera" black = "Beltza" blue = "Urdina" @@ -106,6 +105,7 @@ undoSuccess = "Eragiketa ongi desegin da" unknown = "Ezezaguna" unpin = "Kendu aingura fitxategiari (ordeztu tresna exekutatu ondoren)" unsavedChanges = "Gorde gabeko aldaketak dituzu zure PDFan." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Gorde gabeko aldaketak" unsupported = "Ez da onartzen" username = "Erabiltzaile izena" @@ -1879,6 +1879,9 @@ width = "Zabalera" [app] description = "Adobe Acrobat-en doako alternatiba (10M+ deskarga)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Bihurtu PDF/A-3b formatura" convertToPdfA3bDescription = "Eranskinak txertatuta dituen artxibatzeko PDF bat sortzen du" @@ -2933,12 +2936,16 @@ text = "Baimen hauek aldaezinak bihurtzeko, erabili Gehitu Pasahitza tresna jabe [chat.actions] copy = "Kopiatu mezua" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling agentearen aukerak" +assistant = "AI Assistant" clearChat = "Garbitu txata" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Eragiketa honek zure hodeiko kredituak erabiliko ditu" tooltip = "Hautatu kolorea pantailatik" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Aukeratu kolorea" [common] +actions = "Actions" back = "Atzera" cancel = "Utzi" close = "Itxi" +codeSample = "Code sample" collapse = "Tolestu" confirm = "Berretsi" continue = "Jarraitu" copied = "Kopiatuta!" copy = "Kopiatu" done = "Eginda" +enterValue = "Enter value" error = "Errorea" expand = "Zabaldu" loading = "Kargatzen..." next = "Hurrengoa" +open = "Open" preview = "Aurrebista" previous = "Aurrekoa" refresh = "Freskatu" remaining = "Geratzen dena" retry = "Saiatu berriro" save = "Gorde" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Garbitu hautatutakoa" @@ -3185,6 +3199,7 @@ title = "Konpresio-metodoa" [compress.settings] desiredSize = "Nahi den fitxategi-tamaina" desiredSizePlaceholder = "Sartu tamaina" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Konpresioa modu erraza da fitxategi-tamaina murrizteko. Aukeratu Fitxategiaren tamaina helburu tamaina bat sartzeko eta guk kalitatea doitzeko. Aukeratu Kalitatea konpresio-indarra eskuz ezartzeko." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF aukerak" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Lehenik hautatu iturburu formatua" settings = "Ezarpenak" single = "Bakarra" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Bilatu..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Editatu" @@ -3824,11 +3861,13 @@ shareSelected = "Partekatu hautatutakoak" sharing = "Partekatzea" showAll = "Erakutsi denak" showHistory = "Historia erakutsi" +sortBy = "Sort files" sortByDate = "Ordenatu dataren arabera" sortByName = "Ordenatu izenaren arabera" sortBySize = "Ordenatu tamainaren arabera" storage = "Biltegia" storageState = "Biltegiratzea" +storageUsed = "Storage used" synced = "Sinkronizatuta" title = "PDF fitxategiak igo" toolChain = "Aplikatutako tresnak" @@ -3874,8 +3913,12 @@ addFiles = "Gehitu fitxategiak" addingFiles = "Fitxategiak gehitzen…" collapse = "Tolestu alboko barra" customizeGroups = "Pertsonalizatu taldeak" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Ireki fitxategiak hasteko" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Zabaldu alboko barra" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive ez dago konfiguratuta" @@ -3888,15 +3931,17 @@ openFromComputer = "Ireki ordenagailutik" openSettings = "Ireki ezarpenak" other = "Besteak" recent = "Azkenak" -search = "Bilatu" -searchPlaceholder = "Bilatu fitxategiak..." viewAll = "Ikusi {{count}} fitxategi guztiak" [fileSidebar.fileItem] closeViewer = "Itxi ikustailea" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Ireki ikustailean" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Berrezarri lehenetsietara" subtitle = "Taldekatu zure fitxategiak kategoria nagusietan. Gehitu kategoria bati lehendik dauden edo etiketa berriak, aldatu izena edo sortu zurea. Zure kategorietako batean ez dauden fitxategiak “Besteak” atalean agertuko dira." title = "Alboko barrako kategoriak" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Gehitu lan-eremura" addToWorkspaceCount = "Gehitu {{count}} lan-eremura" @@ -3918,7 +3971,6 @@ backToMyFiles = "Itzuli Nire Fitxategietara" breadcrumbs = "Karpetaren bidea" cancel = "Utzi" classification = "Sailkapena" -clearSearch = "Garbitu bilaketa" clearSelection = "Garbitu hautapena" closeDetails = "Itxi xehetasunak" create = "Sortu" @@ -3950,6 +4002,7 @@ downloadAll = "Deskargatu dena" downloadVersion = "Deskargatu bertsio hau" dropOverlay = "Jaregin fitxategiak igotzeko" dropOverlaySub = "Fitxategiak Tokikoan hasten dira. Erabili 'Eraman hona' edo 'Gorde hodeian' karpeta batean antolatzeko." +duplicate = "Duplicate" file = "Fitxategia" fileInfo = "File info" fileMenu = "Fitxategi-ekintzak" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Aldatu karpeta-zuhaitzaren tamaina (gezi-teklak, Shift pauso save = "Gorde" saveToServer = "Gorde zerbitzarian" saveToServerDisabledHint = "Zerbitzarian gordetzea ez dago gaituta zerbitzari honetan. Eskatu administratzaileari gaitzeko." -search = "Bilatu" -searchPlaceholder = "Bilatu karpeta honetan eta azpikarpetetan" selectAll = "Hautatu dena" selectAllHint = "Egin klik dena hautatzeko. Aholkua: eutsi Ctrl (edo Cmd) tekla fitxategiak banaka gehitzeko, Shift tarte bat hautatzeko." selectedCount = "{{count}} hautatuta" @@ -4044,6 +4095,10 @@ title = "Oraindik ez dago hodeiko fitxategirik" hint = "Igo gabe gordetako fitxategiak hemen geratzen dira. Jaregin fitxategi bat gehitzeko." title = "Ez dago tokiko soilik diren fitxategirik" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Irekiko edo editatuko dituzun fitxategiak hemen agertuko dira." title = "Oraindik ez da ezer aldatu" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Ezin izan da karpeta ezabatu." deleteFolderFailedDetail = "Ezin izan da karpeta ezabatu: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Ezin izan da karpetaren itxura eguneratu." folderAppearanceFailedDetail = "Ezin izan da karpetaren itxura eguneratu: {{message}}" moveFilesFailed = "Ezin izan dira fitxategiak mugitu." @@ -4113,6 +4170,11 @@ localHint = "Nabigatzaile honetan soilik gordeta" shared = "Partekatuak" sharedHint = "Zurekin estekaz partekatuta" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Zaharrenak lehenik" modifiedDesc = "Berrienak lehenik" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Ez dago fitxategirik biltegian. Ireki lehenik fitxategi open = "Ireki" openFile = "Fitxategia ireki" openFiles = "Fitxategiak ireki" +selectFile = "Select {{name}}" selectFromStorage = "Biltegitik hautatu" upload = "Igo" uploadFile = "Fitxategia igo" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Dena gordeta" +analyzingFields = "Analysing form fields..." extractCsvError = "Ezin izan da CSV erauzi" extractXlsxError = "Ezin izan da XLSX erauzi" +filled = "filled" flattenAfterFilling = "Lautu betetze ondoren" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "behar." requiredFieldsError = "Bete beharrezko eremu guztiak" rescanFields = "Eremuak berriro eskaneatu" @@ -4795,9 +4863,6 @@ title = "Irudia PDF bihurtu" [imageToPdf] tags = "conversion,img,jpg,picture,photo,psd,photoshop" -[infoBanner] -dismiss = "Baztertu" - [invite] acceptError = "Ezin izan da kontua sortu" accountFor = "Kontua sortzen honentzat" @@ -4979,6 +5044,34 @@ uploadSuccess = "Kargaketa arrakastatsua!" uploadSuccessMessage = "Zure irudiak transferitu dira." validating = "Saioa balioztatzen..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Eskaneatu argazkiak igotzeko. Irudiak automatikoki PDFra bihurtzen dira." descriptionNoConvert = "Eskaneatu argazkiak zure gailu mugikorretik igotzeko." @@ -4997,8 +5090,13 @@ title = "Kargatu mugikorretik" tags = "Multi Tool,Multi operation,UI,click drag,front end,client side" title = "PDF erabilera anitzeko tresna" -[navbar] -search = "Bilatu" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Autentifikazioa ez da ongi burutu. Leiho hau itxi eta saiatu berriro." @@ -5127,6 +5225,7 @@ activeFiles = "Fitxategi Aktiboak ikuspegiak tresnan kargatuta allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Itxi" cropSettings = "Orain moztu nahi dugun fitxategia hautatu dugunez, Moztu tresna konfigura dezakegu PDFa moztu nahi dugun eremua aukeratzeko." +dialogLabel = "Onboarding" fileCheckbox = "Fitxategietako bat sakatzean, prozesatzeko hautatzen da. Hainbat fitxategi hauta ditzakezu batch eragiketetarako." fileReplacement = "Aldatutako fitxategiak automatikoki ordezkatuko du jatorrizkoa Lan-mahaian, errazago beste tresna batzuekin berriz prozesatzeko." filesButton = "Sarbide azkarreko barrako Fitxategiak botoiak PDFak igotzea ahalbidetzen dizu tresnekin erabiltzeko." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDFa PDF/A bihurtu" tags = "artxibo,epe-luze,estandar,bihurketa,biltegiratzea,kontserbazioa" title = "PDFa PDF/A bihurtu" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "inprimaketa,estandarra,bihurketa,produkzioa,aurre-inprimaketa,artxiboa" title = "PDFtik PDF/Xera" @@ -6045,6 +6151,7 @@ small = "500 kreditu" xsmall = "100 kreditu" [plan.availablePlans] +currency = "Billing currency" subtitle = "Aukeratu zure beharretara egokitzen den plana" title = "Plan eskuragarriak" @@ -6624,6 +6731,7 @@ reachedTitle = "Hileko gastu-mugara iritsi da" title = "Ezin izan da Stripe ataria ireki" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "{{allowance}} doako PDFetatik erabilita" capSuffix_other = "{{allowance}} doako PDFetatik erabilita" eyebrow = "Processor proba" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Bidali dokumentu bat edo jakinarazpen bat email bidez politika bat exekutatzen denean." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Gorde prozesatutako dokumentuak Nextcloud-en." @@ -7077,11 +7211,6 @@ title = "SDK ofizialak" beta = "Beta" deprecated = "Zaharkitua" -[portal.docs.search] -empty = "Ez dago bat datorren dokumentaziorik" -placeholder = "Bilatu dokumentazioan" -results = "{{count}} emaitza" - [portal.docs.skills] eyebrow = "TREBETASUNAK" lead = "Agenteak tresna bakar gisa deitzen dituen gaitasun integratu eta izendatuak. Trebetasun bakoitza ebaluazioak lotuta dituen eragiketa-kate determinista bat da." @@ -7180,11 +7309,11 @@ editorAction = "Editorea" empty = "Ez dago iragazki honekin bat datorren dokumenturik." rowActions = "Errenkadako ekintzak" sensitiveLabel = "Sentikorra" -sensitiveTitle = "Sentikorra — sarbidea behar da" [portal.documents.table.columns] action = "Pipelinea / Ekintza" document = "Dokumentua" +labels = "Labels" product = "Produktua" status = "Egoera" time = "Ordua" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Azken aldiz ikusita" region = "Eskualdea" status = "Egoera" +target = "Target" version = "Bertsioa" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Ikuspegi honek ustekabeko errore bat izan du. Saiatu berriro, edo retry = "Saiatu berriro" title = "Zerbait gaizki atera da orri honetan" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Atzera" body = "Deskargatu mahaigaineko aplikazioa edo egin zerbitzaria self-hosted." @@ -7380,11 +7560,6 @@ cancel = "Utzi" confirm = "Ezeztatu gakoa" title = "Ezeztatu API gakoa" -[portal.infrastructure.attestationLabel] -attested = "Egiaztatuta" -inScope = "Eremuan" -notApplicable = "E/E" - [portal.infrastructure.audit] filterAriaLabel = "Iragazi auditoria-gertaerak kategoriaren arabera" heading = "Auditoria-erregistroak" @@ -7456,11 +7631,6 @@ info = "Informazioa" success = "Arrakasta" warning = "Abisua" -[portal.infrastructure.certLabel] -certified = "Ziurtatuta" -inProgress = "Abian" -notStarted = "Hasi gabe" - [portal.infrastructure.createKey] cancel = "Utzi" createKey = "Sortu gakoa" @@ -7474,238 +7644,10 @@ subtitleCreated = "Kopiatu sekretu hau orain — ez da berriro erakutsiko." title = "Sortu API gakoa" titleCreated = "Gakoa sortuta" -[portal.infrastructure.deployLabel] -live = "Martxan" -queued = "Ilaran" -rolledBack = "Atzera itzulita" -rolling = "Zabaltzen" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Hedatu duena" -environment = "Ingurunea" -product = "Produktua" -status = "Egoera" -version = "Bertsioa" -when = "Noiz" - -[portal.infrastructure.deployments.recent] -heading = "Azken hedapenak" -subheading = "Produktu eta inguruneetako azken zabalkundeak." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instantziak" -latency = "Latentzia" -load = "Karga" -p99 = "P99" -region = "Eskualdea" -status = "Egoera" -throughput = "Errendimendua" -uptime = "Erabilgarritasun-denbora" -version = "Bertsioa" - -[portal.infrastructure.deployments.regions] -heading = "Eskualdeak" -subheading = "Hedatutako Stirling eskualde bakoitzaren zuzeneko osasuna — latentzia, karga eta zabalkunde-bertsioa." - -[portal.infrastructure.deployments.regions.empty] -description = "Hedatutako eskualdeak hemen agertuko dira zure lan-eremua hornitu ondoren." -title = "Ez dago eskualderik hedatuta" - [portal.infrastructure.keyLabel] active = "Aktibo" revoked = "Ezeztatuta" -[portal.infrastructure.modelLabel] -active = "Aktibo" -degraded = "Degradatuta" -disabled = "Desgaituta" - -[portal.infrastructure.models] -heading = "Modeloak" -msValue = "{{value}} ms" -subheading = "Zure lan-eremuan dokumentuen prozesamendua elikatzen duten modelo-katalogoa eta bideratzea." - -[portal.infrastructure.models.byom] -description = "Erregistratu on-prem edo norberak ostatatutako modelo bat eta lotu eskualde bati datu-egoitzak mugatutako prozesamendurako." -title = "Ekarri zure modeloa" - -[portal.infrastructure.models.catalogue] -heading = "Katalogoa" -sub = "Zure lan-eremurako eskuragarri dauden kudeatutako modeloak, zuzeneko latentziarekin eta kostuarekin." -subEnterprise = "Kudeatutako, norberak ekarritako eta on-prem modeloak — eskualdeka lotzeko aukerarekin." - -[portal.infrastructure.models.catalogue.empty] -description = "Zure lan-eremuko katalogoko modeloak hemen agertuko dira." -title = "Ez dago modelorik erabilgarri" - -[portal.infrastructure.models.columns] -cost = "Kostua" -latency = "Latentzia" -load = "Karga" -model = "Modeloa" -status = "Egoera" -type = "Mota" -version = "Bertsioa" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/dei" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Modelo aktiboak" -avgLatency = "Batez besteko latentzia" -included = "Barne" -monthlySpend = "Hileko modelo-gastua" - -[portal.infrastructure.models.routing] -empty = "Ez dago bideratze-arau konfiguraturik." -heading = "Bideratze-arauak" -sub = "Zein modelok kudeatzen duen eragiketa bakoitza. Lehenetsia aplikatzen da arau zehatzagorik bat ez datorrenean." -subLocked = "Bideratu eragiketak modelo zehatzetara — ordaindutako planetan eskuragarri." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Berritu Pro planera eragiketa eta dokumentu mota bakoitza zein modelok kudeatzen duen kontrolatzeko." -title = "Modeloen bideratzea ordaindutako eginbidea da" - -[portal.infrastructure.models.routingColumns] -default = "Lehenetsia" -docType = "Dokumentu mota" -modelForAria = "{{operation}} eragiketarako modeloa" -operation = "Eragiketa" -routedTo = "Hona bideratua" - -[portal.infrastructure.modelTypeLabel] -classification = "Sailkapena" -extraction = "Erauzketa" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degradatuta" -down = "Erorita" -healthy = "Osasuntsu" - -[portal.infrastructure.security.access.byok] -description = "Eman zure KMS propioaren gako bat. Stirling-ek harekin zifratzen du, baina oraindik irakur dezake." -label = "Ekarri zure gakoa (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Gakoak ez dira inoiz zure KMS-tik irteten. Stirling-ek zifratu-testua baino ez du gordetzen." -label = "Gorde zure gakoa (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling-ek zifratze-gakoak kudeatzen ditu. Errazena — zure aldetik gako-eragiketarik gabe." -label = "Stirling-ek gordetako gakoak" - -[portal.infrastructure.security.accessPolicy] -heading = "Dokumentuetarako sarbide-gidalerroa" -subheading = "Atsedenean dauden prozesatutako dokumentuak nork deszifratu ditzakeen kontrolatzen du." - -[portal.infrastructure.security.attestations] -heading = "Betetze-egiaztapenak" -noReport = "Ez dago txostenik eskuragarri" -subheading = "Esparruz esparruko auditoria-jarrera, egiaztatutako kontroletan txostenak eskuragarri direla." -viewReport = "Ikusi txostena →" - -[portal.infrastructure.security.compliance] -heading = "Betetzea" -subheading = "Stirling plataforma estaltzen duten egiaztapenak eta ziurtagiriak." - -[portal.infrastructure.security.empty] -description = "Zure lan-eremuko segurtasun-konfigurazioa hemen agertuko da." -title = "Segurtasun-jarrera ez dago eskuragarri" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK erabiliz, zifratze-gakoak ez dira inoiz zure KMS-tik irteten. Stirling-ek edozein unetan baliogabetu dezakezun zifratu-testua baino ez du gordetzen eta prozesatzen." -title = "Stirling-ek ezin ditu zure dokumentuak deszifratu" - -[portal.infrastructure.security.ipAllowlist] -empty = "Ez dago IP barrutirik konfiguratuta — IP guztiak baimenduta daude." -heading = "IP baimen-zerrenda" -sub = "API sarbidea CIDR barruti hauetara mugatuta dago." -subLocked = "Mugatu API sarbidea IP barruti ezagunetara — ordaindutako planetan eskuragarri." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Berritu Pro planera API sarbidea sare zehatzetara mugatzeko." -title = "IP baimen-zerrendak ordaindutako eginbidea dira" - -[portal.infrastructure.security.ipColumns] -added = "Gehituta" -addedBy = "Gehitu duena" -cidr = "CIDR" -label = "Etiketa" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritmoa" -heading = "Zifratze-gakoen kudeaketa" -keyId = "Gako-identifikatzailea" -lastRotated = "Azken biraketa" -rotateKey = "Biratu gakoa" -rotationPolicy = "Biraketa-gidalerroa" -subheading = "Atsedenean dokumentuak zifratzen dituzten gakoen zaintza — nork deszifratu dezakeen eta gakoak nola biratzen diren." - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key (BYOK) eta hold-your-own-key (HYOK) zaintza Enterprise planean daude eskuragarri. Berritu zure KMS propioko gakoak emateko." -title = "Zure planean, gakoak Stirling-ek kudeatzen ditu" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia Pazifikoa" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR datu-muga" -label = "Europar Batasuna" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Estatu Batuak" - -[portal.infrastructure.security.residencyHeader] -heading = "Datu-egoitza" -subheading = "Dokumentuak non gordetzen eta prozesatzen diren." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} erabilita" - -[portal.infrastructure.storage.empty] -description = "Konektatutako biltegiratzea eta erabilera hemen agertuko dira." -title = "Ez dago biltegiratzerik konfiguratuta" - -[portal.infrastructure.storage.lifecycle] -active = "Aktibo" -activeRange = "0–{{value}}d" -archived = "Artxibatuta" -coldStorage = "biltegiratze hotza" -deleted = "Ezabatuta" -never = "inoiz ez" -purged = "garbituta" - -[portal.infrastructure.storage.providers] -connect = "Konektatu" -connected = "Konektatuta" -heading = "Konektatutako hornitzaileak" -subheading = "Prozesatutako artefaktuak non idazten diren." - -[portal.infrastructure.storage.retention] -heading = "Atxikipena" -subheading = "Artefaktuak bizi-zikloko ezabaketaren aurretik zenbat denboraz mantentzen diren." -windowLabel = "Atxikipen-leiho lehenetsia" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} egun" -days_other = "{{count}} egun" -never = "Ez ezabatu inoiz" - -[portal.infrastructure.storage.totalUsage] -heading = "Erabilera guztira" -progressLabel = "Erabilitako biltegiratzea" -subheading = "Konektatutako hornitzaile guztietan kontsumitutako biltegiratzea." - [portal.infrastructure.tabs] apiKeys = "API gakoak" audit = "Auditoria-erregistroak" @@ -7715,11 +7657,9 @@ security = "Segurtasuna" storage = "Biltegiratzea" [portal.integrations] -addAnother = "Gehitu beste bat" availableHeading = "Eskuragarri" comingSoonHeading = "Laster" connect = "Konektatu" -connectedHeading = "Konektatuta" connectionCount_one = "{{count}} konexio" connectionCount_other = "{{count}} konexio" customApi = "API pertsonalizatua" @@ -7737,6 +7677,10 @@ security = "Segurtasuna" signing = "Sinatzea" storage = "Biltegiratzea" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Konektatuta" @@ -7767,7 +7711,6 @@ integrations = "Integrazioak" pipelines = "Pipelineak" policies = "Gidalerroak" procurement = "Erosketak" -settings = "Ezarpenak" sources = "Iturburuak" usage = "Erabilera eta fakturazioa" users = "Erabiltzaileak" @@ -7784,7 +7727,7 @@ title = "Pipelineak" newPipeline = "Pipeline berria" [portal.pipelines.builder] -addStep = "Gehitu tresna" +activate = "Activate" back = "Itzuli pipelineetara" cannotFollow = "Ezin du {{produced}} hartu" chooseAccount = "Aukeratu kontu bat" @@ -7792,28 +7735,38 @@ chooseDestination = "Aukeratu helmuga bat" chooseOperation = "Aukeratu urrats honek zer egiten duen" chooseSource = "Aukeratu iturburu bat" discard = "Baztertu aldaketak" -enabled = "Gaituta" inputs = "Sarrera" inputSource = "Sarrera-iturburua" inputTrigger = "Abiarazlea" keepEditing = "Jarraitu editatzen" +moreActions = "More actions" needsConfiguring = "Konfiguratu behar da" -needsUpload = "Kargatutako fitxategi bat behar du" -noSources = "Oraindik ez dago iturbururik konektatuta. Sortu bat sarrera gisa erabiltzeko." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Ez dago zure bilaketarekin bat datorren tresnarik." -pipelineSettings = "Pipelinearen ezarpenak" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Bilatu tresnak" -selectToolBody = "Gehitu tresna bat zure pipelinea eraikitzeko." -selectToolTitle = "Oraindik ez dago tresnarik" sendToSystem = "Bidali beste sistema batera" stepsIncompatible = "Urrats hauek ezin dira exekutatu aurreko urratsak sortzen duenarekin: {{tools}}." stepsNeedSetup = "Urrats hauek oraindik konfiguratu behar dira gorde aurretik: {{tools}}." -toolSettings = "Tresnaren ezarpenak" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Eragiketa ezezaguna, bere horretan mantendu da." unsavedBody = "Gorde gabeko aldaketak dituzu. Gorde irten aurretik, edo baztertu?" unsavedTitle = "Gorde gabeko aldaketak" -uploadUnsupported = "Kargatutako fitxategiak oraindik ez dira onartzen pipelineetan; beraz, urrats hauek ezin dira gorde: {{tools}}." usesDefaults = "Ezarpen lehenetsiekin exekutatzen da" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Aurreko urratseko fitxategi guztiak konbinatzen ditu" @@ -7825,31 +7778,36 @@ undeclared-operation = "Ezin da egiaztatu urrats honek zer onartzen duen" [portal.pipelines.composer] addTool = "Gehitu tresna" -cancel = "Utzi" -chainEmpty = "Gehitu tresna bat zure pipelinea eraikitzen hasteko." create = "Sortu pipelinea" +createPaused = "Create paused" editingUnsupported = "Tresna-parametro hauek editatzeko bistaratzea oraindik ez da onartzen." -moveDown = "Mugitu behera" -moveUp = "Mugitu gora" +editSource = "Edit source" name = "Izena" namePlaceholder = "adib. Erredakzio-ekorketa" noToolSettings = "Tresna honek ez du ezarpen konfiguragarririk." -operations_one = "Eragiketa ({{count}})" -operations_other = "Eragiketak ({{count}})" output = "Irteera" -removeStep = "Kendu eragiketa" save = "Gorde aldaketak" scheduleEvery = "Exekutatu behin" -sources = "Iturburuak" -sourcesLoading = "Iturburuak kargatzen..." trigger = "Abiarazlea" triggerManual = "Eskuz bakarrik" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "egun" hours = "ordu" minutes = "minutu" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Ezabatu \"{{name}}\"? Hau ezin da desegin." cancel = "Utzi" @@ -7867,6 +7825,37 @@ connectSource = "Konektatu iturburu bat" description = "Sortu zure lehen pipelinea: aukeratu zein iturburutan exekutatuko den, kateatu eragiketak eta aukeratu irteera nora joango den." title = "Oraindik ez dago pipelinerik" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktibo" paused = "Pausatuta" @@ -7879,7 +7868,6 @@ completed_one = "Exekuzioa osatu da." completed_other = "{{count}} exekuzio guztiak osatu dira." empty = "Ez dago exekutatzeko ezer: iturburuek ez zuten prozesatzeko dokumenturik." failed = "Exekuzioak huts egin du: {{error}}" -historyCleared = "Historia garbitu da. Hurrengo exekuzioak une honetan iturburuetan dagoen guztia berriro prozesatuko du." inFlight = "Ez dago exekutatzeko ezer berririk: dokumentuak oraindik aurreko exekuzio batetik prozesatzen ari dira." parked_one = "Ez dago exekutatzeko ezer: {{count}} dokumentuk huts egin zuen lehenago eta aparkatuta dago. Konpondu arrazoia, eta gero garbitu historia berriro saiatzeko." parked_other = "Ez dago exekutatzeko ezer: {{count}} dokumentuk huts egin zuten lehenago eta aparkatuta daude. Konpondu arrazoia, eta gero garbitu historia berriro saiatzeko." @@ -7895,6 +7883,7 @@ name = "Pipelinea" sources = "Iturburuak" status = "Egoera" steps = "Urratsak" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Karpetaren zaintza" @@ -8209,6 +8198,24 @@ label = "Erantsi Jira issue bati" description = "Prozesatutako dokumentua eranskin gisa bidaltzen du." label = "Bidali dokumentua email bidez (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Prozesatutako dokumentua karpeta batean idazten du." label = "Igo Nextcloud-era" @@ -8683,16 +8690,6 @@ maxed = "Mugara iritsita" subtitle = "Zure doako probaldia {{date}} arte doa. Ez da txartelik behar." title = "Enterprise probaldia" -[portal.search] -ariaLabel = "Bilatu" -placeholder = "Bilatu Stirling — endpointak, pipelineak, dokumentuak…" - -[portal.search.empty] -noActionsDescription = "Ekintza azkarrak hemen agertuko dira erabilgarri daudenean." -noActionsTitle = "Ez dago ekintza azkarrik" -noMatches = "Ez dago bat-etortzerik \"{{query}}\"-rentzat" -noMatchesDescription = "Saiatu beste gako-hitz batekin edo arakatu katalogoa." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Utzi" confirm = "Ezabatu" title = "Ezabatu iturburua?" -[portal.sources.empty] -description = "Konektatu karpeta bat (eta, laster, hodeiko biltegiratzea), zure politikek dokumentuak nondik hartu izan dezaten." -title = "Oraindik ez dago iturbururik konektatuta" - [portal.sources.kpi] inUse = "Erabiltzen" total = "Konexioak" @@ -8785,6 +8778,7 @@ unused = "Erabili gabe" documents = "Dokumentuak" source = "Iturburua" status = "Egoera" +type = "Type" usedBy = "Politikak" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Politikak eta Pribatutasuna" [settings.preferences] title = "Hobespenak" -[settings.search] -placeholder = "Bilatu ezarpen-orrietan..." - [settings.security] description = "Eguneratu pasahitza kontua seguru mantentzeko." title = "Segurtasuna" @@ -10128,6 +10119,7 @@ last = "Azken orria" maintainRatio = "Aldatu aspektu-erlazioa mantentzea" next = "Hurrengo orria" noSavedSigs = "Ez da gordetako sinadurarik aurkitu" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Sinadura pertsonalak" previous = "Aurreko orria" redo = "Berregin" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Atzeko planoa kentzeak huts egin du" hint = "Igo zure sinaduraren PNG edo JPG irudia" label = "Igo sinaduraren irudia" placeholder = "Hautatu irudi-fitxategia" +previewAlt = "Current image signature" processing = "Irudia prozesatzen..." removeBackground = "Kendu atzeko plano zuria (gardendu)" @@ -10179,6 +10172,17 @@ saved = "Hautatu goiko gordetako sinadura, ondoren egin klik PDFan edozein lekut text = "Goian zure izena sartu ondoren, egin klik PDFn edonon zure sinadura kokatzeko." title = "Nola gehitu sinadura" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Mugitu sinadura" pause = "Kokatzea pausatu" @@ -10625,6 +10629,23 @@ title = "Igo zerbitzarira" updateButton = "Eguneratu zerbitzarian" uploadButton = "Igo zerbitzarira" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Itzuli atzera" pdfTools = "PDF tresnak" placeholder = "Aukeratu tresna bat hasteko" premiumFeature = "Premium ezaugarria:" -searchTools = "Bilatu tresnak" toolsHeader = "Tresnak" viewAllTools = "Ikusi tresna guztiak" @@ -10822,6 +10842,9 @@ removal = "Kentzea" signing = "Sinatzea" verification = "Egiaztapena" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Ez da tresnarik aurkitu" noTools = "Ez dago tresnarik eskuragarri" @@ -10979,6 +11002,13 @@ approver = "Politika onartzen du" editor = "Editorea" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "{{email}} helbiderako gonbidapena bertan behera utzi? Ezingo dute uneko estekarekin bat egin." cancelInviteTitle = "Utzi gonbidapena bertan behera" @@ -10998,10 +11028,8 @@ title = "Oraindik ez dago kiderik" addToTeam = "Gehitu taldera" guestCount = "{{count}} gonbidatu" guests = "Gonbidatuak" -guestsDesc = "Kanpoko kolaboratzaileak, partekatu duzunera mugatuak. Editorea soilik." ledBy = "{{owner}}-k gidatua" org = "Erakundea" -orgDesc = "Erakunde osoko agintea eta politika-onarpena dituzten jabeak" owners = "{{count}} jabe" team = "{{name}} taldea" teamMeta = "{{count}} pertsona" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "{{who}} erabiltzaileak gonbidatua" cancel = "Utzi bertan behera" -count = "{{count}} zain" -desc = "Oraindik sartu ez diren gonbidatuak. Eserleku bat mantentzen dute onartu arte." expiresInDays_one = "{{count}} egun barru iraungiko da" expiresInDays_other = "{{count}} egun barru iraungiko da" expiresToday = "Gaur iraungiko da" title = "Zain dauden gonbidapenak" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Zerbait gaizki joan da backend-era konektatzean, edo ez duzu sarbiderik. Saiatu berriro." title = "Ezin izan dira kideak kargatu" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Bilatu eranskinak" title = "Eranskinak" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Laster-markaren izenburua" bookmarkTitleRequired = "Laster-markaren izenburua beharrezkoa da" closeSidebar = "Itxi laster-marken alboko barra" collapseAll = "Tolestu laster-marka guztiak" +empty = "No bookmarks in this document" expandAll = "Zabaldu laster-marka guztiak" searchPlaceholder = "Bilatu laster-markak" +title = "Bookmarks" [viewer.comments] addComment = "Gehitu iruzkina" @@ -11264,10 +11297,12 @@ locateAnnotation = "Kokatu dokumentuan" moreActions = "Ekintza gehiago" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Orrialdea {{page}}" placingHint = "Egin klik orri batean kokatzeko… (utzi)" removeCommentOnly = "Kendu iruzkina bakarrik" saveReply = "Gorde erantzuna" +searchPlaceholder = "Search comments" title = "Iruzkinak" typeComment = "Iruzkina" typeInsertText = "Testua txertatu" @@ -11290,6 +11325,7 @@ unsavedDesc = "Gorde gabeko aldaketak dituzu" closeSidebar = "Itxi geruzen alboko barra" hideAll = "Ezkutatu geruza guztiak" showAll = "Erakutsi geruza guztiak" +title = "Layers" [viewer.link] delete = "Ezabatu esteka" @@ -11646,6 +11682,7 @@ exitRedaction = "Irten zentsura modutik" exportAll = "Esportatu PDFa" exportSelected = "Esportatu hautatutako orriak" formFill = "Bete inprimakia" +hideToolbar = "Hide toolbar" multiTool = "Multi-tresna" panMode = "Pan modua" print = "Inprimatu PDFa" @@ -11666,6 +11703,7 @@ selectAll = "Hautatu dena" selectByNumber = "Hautatu orri-zenbakien arabera" selectLanguage = "Hautatu hizkuntza" share = "Partekatu" +showToolbar = "Show toolbar" toggleAnnotations = "Txandakatu anotazioen ikusgarritasuna" toggleAttachments = "Txandakatu eranskinak" toggleBookmarks = "Txandakatu laster-markak" diff --git a/frontend/editor/public/locales/fa-IR/translation.toml b/frontend/editor/public/locales/fa-IR/translation.toml index fdde8d6ae8..8dabf9598d 100644 --- a/frontend/editor/public/locales/fa-IR/translation.toml +++ b/frontend/editor/public/locales/fa-IR/translation.toml @@ -1,7 +1,6 @@ alphabet = "حروف الفبا" apply = "اعمال" applyAndContinue = "ذخیره و خروج" -areYouSure = "مطمئنید می‌خواهید خارج شوید؟" back = "بازگشت" black = "سیاه" blue = "آبی" @@ -106,6 +105,7 @@ undoSuccess = "عملیات با موفقیت واگردانی شد" unknown = "نامعلوم" unpin = "برداشتن سنجاق فایل (پس از اجرای ابزار جایگزین شود)" unsavedChanges = "شما تغییرات ذخیره‌نشده‌ای در PDF دارید." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "تغییرات ذخیره‌نشده" unsupported = "پشتیبانی نمی‌شود" username = "نام کاربری" @@ -1879,6 +1879,9 @@ width = "عرض" [app] description = "جایگزین رایگان Adobe Acrobat (10M+ دانلود)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "تبدیل به PDF/A-3b" convertToPdfA3bDescription = "یک PDF آرشیوی با پیوست‌های توکار ایجاد می‌کند" @@ -2933,12 +2936,16 @@ text = "برای غیرقابل‌تغییر کردن این مجوزها، از [chat.actions] copy = "کپی پیام" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "گزینه‌های عامل Stirling" +assistant = "AI Assistant" clearChat = "پاک‌کردن چت" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "این عملیات از اعتبارهای ابری شما استف tooltip = "برداشت رنگ از صفحه" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "انتخاب رنگ" [common] +actions = "Actions" back = "بازگشت" cancel = "انصراف" close = "بستن" +codeSample = "Code sample" collapse = "جمع کردن" confirm = "تأیید" continue = "ادامه" copied = "کپی شد!" copy = "کپی" done = "انجام شد" +enterValue = "Enter value" error = "خطا" expand = "گسترش دادن" loading = "در حال بارگذاری..." next = "بعدی" +open = "Open" preview = "پیش‌نمایش" previous = "قبلی" refresh = "تازه‌سازی" remaining = "باقی‌مانده" retry = "تلاش مجدد" save = "ذخیره" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "پاک کردن انتخاب‌شده‌ها" @@ -3185,6 +3199,7 @@ title = "روش فشرده‌سازی" [compress.settings] desiredSize = "حجم فایل موردنظر" desiredSizePlaceholder = "اندازه را وارد کنید" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "فشرده‌سازی راهی آسان برای کاهش اندازه فایل است. «اندازه فایل» را انتخاب کنید تا اندازه هدف را وارد کنید و ما کیفیت را برای شما تنظیم کنیم. «کیفیت» را انتخاب کنید تا قدرت فشرده‌سازی را به‌صورت دستی تنظیم کنید." @@ -3387,6 +3402,24 @@ pdfOptions = "گزینه‌های PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "ابتدا قالب مبدأ را انتخاب کنید" settings = "تنظیمات" single = "تکی" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "جستجو..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "ویرایش" @@ -3824,11 +3861,13 @@ shareSelected = "اشتراک‌گذاری موارد انتخاب‌شده" sharing = "اشتراک‌گذاری" showAll = "نمایش همه" showHistory = "نمایش تاریخچه" +sortBy = "Sort files" sortByDate = "مرتب‌سازی بر اساس تاریخ" sortByName = "مرتب‌سازی بر اساس نام" sortBySize = "مرتب‌سازی بر اساس حجم" storage = "فضای ذخیره" storageState = "ذخیره‌سازی" +storageUsed = "Storage used" synced = "همگام شد" title = "بارگذاری فایل‌های PDF" toolChain = "ابزارهای اعمال‌شده" @@ -3874,8 +3913,12 @@ addFiles = "افزودن فایل‌ها" addingFiles = "در حال افزودن فایل‌ها…" collapse = "جمع کردن نوار کناری" customizeGroups = "سفارشی‌سازی گروه‌ها" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "برای شروع فایل‌ها را باز کنید" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "گسترش نوار کناری" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive پیکربندی نشده است" @@ -3888,15 +3931,17 @@ openFromComputer = "باز کردن از رایانه" openSettings = "باز کردن تنظیمات" other = "سایر" recent = "اخیر" -search = "جستجو" -searchPlaceholder = "جستجوی فایل‌ها..." viewAll = "مشاهده همه {{count}} فایل" [fileSidebar.fileItem] closeViewer = "بستن نمایشگر" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "باز کردن در نمایشگر" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "بازنشانی به پیش‌فرض‌ها" subtitle = "فایل‌های خود را در دسته‌های والد گروه‌بندی کنید. برچسب‌های موجود یا جدید را به یک دسته اضافه کنید، نام آن را تغییر دهید، یا دسته خودتان را بسازید. فایل‌هایی که در هیچ‌یک از دسته‌های شما نیستند زیر «سایر» نمایش داده می‌شوند." title = "دسته‌های نوار کناری" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "افزودن به فضای کاری" addToWorkspaceCount = "افزودن {{count}} مورد به فضای کاری" @@ -3918,7 +3971,6 @@ backToMyFiles = "بازگشت به فایل‌های من" breadcrumbs = "مسیر پوشه" cancel = "لغو" classification = "طبقه‌بندی" -clearSearch = "پاک‌کردن جستجو" clearSelection = "پاک‌کردن انتخاب" closeDetails = "بستن جزئیات" create = "ایجاد" @@ -3950,6 +4002,7 @@ downloadAll = "دانلود همه" downloadVersion = "دانلود این نسخه" dropOverlay = "فایل‌ها را برای بارگذاری رها کنید" dropOverlaySub = "فایل‌ها در «محلی» شروع می‌شوند. برای سازمان‌دهی آن‌ها در یک پوشه از 'انتقال به' یا 'ذخیره در ابر' استفاده کنید." +duplicate = "Duplicate" file = "فایل" fileInfo = "File info" fileMenu = "اقدامات فایل" @@ -3983,8 +4036,6 @@ resizeFolderTree = "تغییر اندازهٔ درخت پوشه‌ها (کلید save = "ذخیره" saveToServer = "ذخیره در سرور" saveToServerDisabledHint = "ذخیره در سرور روی این سرور فعال نیست. از مدیر خود بخواهید آن را فعال کند." -search = "جستجو" -searchPlaceholder = "جستجوی این پوشه و زیرپوشه‌ها" selectAll = "انتخاب همه" selectAllHint = "برای انتخاب همه کلیک کنید. نکته: برای افزودن فایل‌ها به‌صورت تکی Ctrl (یا Cmd) را نگه دارید، و برای انتخاب یک بازه از Shift استفاده کنید." selectedCount = "{{count}} مورد انتخاب‌شده" @@ -4044,6 +4095,10 @@ title = "هنوز فایلی در ابر نیست" hint = "فایل‌هایی که بدون بارگذاری ذخیره می‌کنید اینجا می‌مانند. برای افزودن، یک فایل را رها کنید." title = "هیچ فایل صرفاً محلی نیست" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "فایل‌هایی که باز یا ویرایش می‌کنید اینجا نمایش داده می‌شوند." title = "هنوز موردی تغییر نکرده است" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "امکان حذف پوشه نبود." deleteFolderFailedDetail = "امکان حذف پوشه نبود: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "امکان به‌روزرسانی ظاهر پوشه نبود." folderAppearanceFailedDetail = "امکان به‌روزرسانی ظاهر پوشه نبود: {{message}}" moveFilesFailed = "امکان جابه‌جایی فایل‌ها نبود." @@ -4113,6 +4170,11 @@ localHint = "فقط در این مرورگر ذخیره شده است" shared = "اشتراکی" sharedHint = "از طریق لینک با شما به اشتراک گذاشته شده" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "قدیمی‌ترین ابتدا" modifiedDesc = "جدیدترین ابتدا" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "فایلی در فضای ذخیره‌سازی موجو open = "باز کردن" openFile = "باز کردن فایل" openFiles = "باز کردن فایل‌ها" +selectFile = "Select {{name}}" selectFromStorage = "انتخاب از فضای ذخیره" upload = "بارگذاری" uploadFile = "بارگذاری فایل" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "همه ذخیره شد" +analyzingFields = "Analysing form fields..." extractCsvError = "استخراج CSV ناموفق بود" extractXlsxError = "استخراج XLSX ناموفق بود" +filled = "filled" flattenAfterFilling = "مسطح‌سازی پس از پرکردن" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "الزامی" requiredFieldsError = "لطفاً همه فیلدهای الزامی را پر کنید" rescanFields = "اسکن دوبارهٔ فیلدها" @@ -4795,9 +4863,6 @@ title = "تبدیل تصویر به PDF" [imageToPdf] tags = "تبدیل،عکس،jpg،تصویر،عکس" -[infoBanner] -dismiss = "بستن" - [invite] acceptError = "ایجاد حساب ناموفق بود" accountFor = "در حال ایجاد حساب برای" @@ -4979,6 +5044,34 @@ uploadSuccess = "آپلود موفق!" uploadSuccessMessage = "تصاویر شما منتقل شدند." validating = "در حال اعتبارسنجی نشست..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "برای آپلود عکس‌ها اسکن کنید. تصاویر به‌صورت خودکار به PDF تبدیل می‌شوند." descriptionNoConvert = "برای آپلود عکس‌ها از دستگاه موبایل خود اسکن کنید." @@ -4997,8 +5090,13 @@ title = "آپلود از موبایل" tags = "ابزار چندگانه،عملیات چندگانه،واسط کاربری،کلیک و کشیدن،فرانت‌اند،کاربردی،قابل تعامل،جابجایی،حذف،تقسیم" title = "ابزار چندگانه PDF" -[navbar] -search = "جستجو" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "احراز هویت موفق نبود. می‌توانید این پنجره را ببندید و دوباره تلاش کنید." @@ -5127,6 +5225,7 @@ activeFiles = "نمای Active Files همه PDFهایی را ک allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "بستن" cropSettings = "حالا که فایلی را که می‌خواهیم برش دهیم انتخاب کرده‌ایم، می‌توانیم ابزار Crop را پیکربندی کنیم تا ناحیه‌ای را که می‌خواهیم PDF به آن برش داده شود انتخاب کنیم." +dialogLabel = "Onboarding" fileCheckbox = "با کلیک روی یکی از فایل‌ها، آن برای پردازش انتخاب می‌شود. می‌توانید چندین فایل را برای عملیات دسته‌ای انتخاب کنید." fileReplacement = "فایل تغییر‌یافته به‌طور خودکار در Workbench جایگزین فایل اصلی می‌شود و امکان می‌دهد به‌راحتی آن را از ابزارهای بیشتری عبور دهید." filesButton = "دکمه Files در نوار دسترسی سریع به شما امکان می‌دهد PDFها را برای استفاده در ابزارها بارگذاری کنید." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF به PDF/A" tags = "آرشیو،ذخیره‌سازی بلندمدت،استاندارد،تبدیل،ذخیره‌سازی،حفظ" title = "PDF به PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "چاپ,استاندارد,تبدیل,تولید,پیش‌ازچاپ,آرشیو" title = "تبدیل PDF به PDF/X" @@ -6045,6 +6151,7 @@ small = "500 اعتبار" xsmall = "100 اعتبار" [plan.availablePlans] +currency = "Billing currency" subtitle = "طرحی را انتخاب کنید که با نیازهای شما سازگار است" title = "طرح‌های موجود" @@ -6624,6 +6731,7 @@ reachedTitle = "حد هزینه ماهانه رسیده است" title = "امکان باز کردن پورتال Stripe وجود نداشت" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "از {{allowance}} PDF رایگان استفاده شده" capSuffix_other = "از {{allowance}} PDF رایگان استفاده شده" eyebrow = "آزمایشی Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "وقتی یک خط‌مشی اجرا می‌شود، سند یا اعلان را ایمیل کنید." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "اسناد پردازش‌شده را در Nextcloud قرار دهید." @@ -7077,11 +7211,6 @@ title = "SDKهای رسمی" beta = "Beta" deprecated = "منسوخ‌شده" -[portal.docs.search] -empty = "مستندات مطابقی وجود ندارد" -placeholder = "جست‌وجوی مستندات" -results = "{{count}} نتیجه" - [portal.docs.skills] eyebrow = "مهارت‌ها" lead = "قابلیت‌های بسته‌بندی‌شده و نام‌دار که عامل شما به‌عنوان یک ابزار واحد فراخوانی می‌کند. هر مهارت یک زنجیره عملیات قطعی با ارزیابی‌های پیوست‌شده است." @@ -7180,11 +7309,11 @@ editorAction = "ویرایشگر" empty = "هیچ سندی با این فیلتر مطابقت ندارد." rowActions = "اقدامات ردیف" sensitiveLabel = "حساس" -sensitiveTitle = "حساس — دسترسی لازم است" [portal.documents.table.columns] action = "Pipeline / اقدام" document = "سند" +labels = "Labels" product = "محصول" status = "وضعیت" time = "زمان" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "آخرین مشاهده" region = "Region" status = "وضعیت" +target = "Target" version = "نسخه" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "این نما با خطای غیرمنتظره‌ای روبه‌ retry = "دوباره تلاش کنید" title = "در این صفحه مشکلی پیش آمد" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "بازگشت" body = "برنامه دسکتاپ را دانلود کنید یا سرور را خودمیزبان کنید." @@ -7380,11 +7560,6 @@ cancel = "لغو" confirm = "لغو کلید" title = "لغو کلید API" -[portal.infrastructure.attestationLabel] -attested = "تأییدشده" -inScope = "در محدوده" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "فیلتر کردن رویدادهای حسابرسی بر اساس دسته" heading = "گزارش‌های حسابرسی" @@ -7456,11 +7631,6 @@ info = "اطلاعات" success = "موفقیت" warning = "هشدار" -[portal.infrastructure.certLabel] -certified = "گواهی‌شده" -inProgress = "در حال انجام" -notStarted = "شروع نشده" - [portal.infrastructure.createKey] cancel = "لغو" createKey = "ایجاد کلید" @@ -7474,238 +7644,10 @@ subtitleCreated = "این secret را اکنون کپی کنید — دوبار title = "ایجاد کلید API" titleCreated = "کلید ایجاد شد" -[portal.infrastructure.deployLabel] -live = "فعال" -queued = "در صف" -rolledBack = "بازگردانی‌شده" -rolling = "در حال انتشار" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/دقیقه" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "مستقرشده توسط" -environment = "محیط" -product = "محصول" -status = "وضعیت" -version = "نسخه" -when = "زمان" - -[portal.infrastructure.deployments.recent] -heading = "استقرارهای اخیر" -subheading = "آخرین rolloutها در محصولات و محیط‌ها." - -[portal.infrastructure.deployments.regionColumns] -instances = "نمونه‌ها" -latency = "Latency" -load = "بار" -p99 = "P99" -region = "Region" -status = "وضعیت" -throughput = "Throughput" -uptime = "Uptime" -version = "نسخه" - -[portal.infrastructure.deployments.regions] -heading = "Regions" -subheading = "سلامت زنده برای هر Stirling region مستقرشده — latency، بار و نسخه rollout." - -[portal.infrastructure.deployments.regions.empty] -description = "پس از تأمین فضای کاری شما، regionهای مستقرشده اینجا نمایش داده می‌شوند." -title = "هیچ regionی مستقر نشده است" - [portal.infrastructure.keyLabel] active = "فعال" revoked = "لغوشده" -[portal.infrastructure.modelLabel] -active = "فعال" -degraded = "دارای اختلال" -disabled = "غیرفعال" - -[portal.infrastructure.models] -heading = "مدل‌ها" -msValue = "{{value}} ms" -subheading = "کاتالوگ مدل و routing که پردازش سند را در سراسر فضای کاری شما توانمند می‌کند." - -[portal.infrastructure.models.byom] -description = "یک مدل on-prem یا خودمیزبان ثبت کنید و آن را برای پردازش محدود به data residency به یک region پین کنید." -title = "مدل خودتان را بیاورید" - -[portal.infrastructure.models.catalogue] -heading = "کاتالوگ" -sub = "مدل‌های مدیریت‌شده در دسترس فضای کاری شما، با latency و هزینه زنده." -subEnterprise = "مدل‌های مدیریت‌شده، bring-your-own و on-prem — با امکان پین کردن برای هر region." - -[portal.infrastructure.models.catalogue.empty] -description = "مدل‌های موجود در کاتالوگ فضای کاری شما اینجا نمایش داده می‌شوند." -title = "هیچ مدلی در دسترس نیست" - -[portal.infrastructure.models.columns] -cost = "هزینه" -latency = "Latency" -load = "بار" -model = "مدل" -status = "وضعیت" -type = "نوع" -version = "نسخه" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/فراخوانی" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "مدل‌های فعال" -avgLatency = "میانگین latency" -included = "شامل‌شده" -monthlySpend = "هزینه ماهانه مدل" - -[portal.infrastructure.models.routing] -empty = "هیچ قانون routing پیکربندی نشده است." -heading = "قوانین routing" -sub = "کدام مدل هر عملیات را مدیریت می‌کند. حالت پیش‌فرض زمانی اعمال می‌شود که هیچ قانون محدودتری مطابقت نداشته باشد." -subLocked = "هدایت عملیات به مدل‌های مشخص — در طرح‌های پولی در دسترس است." - -[portal.infrastructure.models.routing.lockedBanner] -description = "برای کنترل اینکه کدام مدل هر عملیات و نوع سند را مدیریت کند، به Pro ارتقا دهید." -title = "Model routing یک قابلیت پولی است" - -[portal.infrastructure.models.routingColumns] -default = "پیش‌فرض" -docType = "نوع سند" -modelForAria = "مدل برای {{operation}}" -operation = "عملیات" -routedTo = "هدایت‌شده به" - -[portal.infrastructure.modelTypeLabel] -classification = "طبقه‌بندی" -extraction = "استخراج" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "دارای اختلال" -down = "از کار افتاده" -healthy = "سالم" - -[portal.infrastructure.security.access.byok] -description = "یک کلید از KMS خودتان ارائه دهید. Stirling با آن رمزنگاری می‌کند اما همچنان می‌تواند بخواند." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "کلیدها هرگز KMS شما را ترک نمی‌کنند. Stirling فقط ciphertext را نگه می‌دارد." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling کلیدهای رمزنگاری را مدیریت می‌کند. ساده‌ترین حالت — بدون عملیات کلید از سمت شما." -label = "کلیدهای نگه‌داری‌شده توسط Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "سیاست دسترسی سند" -subheading = "کنترل می‌کند چه کسانی می‌توانند اسناد پردازش‌شده در حالت at rest را رمزگشایی کنند." - -[portal.infrastructure.security.attestations] -heading = "گواهی‌های انطباق" -noReport = "هیچ گزارشی در دسترس نیست" -subheading = "وضعیت حسابرسی چارچوب‌به‌چارچوب، با گزارش‌های در دسترس برای کنترل‌های گواهی‌شده." -viewReport = "مشاهده گزارش →" - -[portal.infrastructure.security.compliance] -heading = "انطباق" -subheading = "گواهی‌ها و تأییدیه‌هایی که پلتفرم Stirling را پوشش می‌دهند." - -[portal.infrastructure.security.empty] -description = "پیکربندی امنیتی فضای کاری شما اینجا نمایش داده می‌شود." -title = "وضعیت امنیتی در دسترس نیست" - -[portal.infrastructure.security.hyokBanner] -description = "با HYOK، کلیدهای رمزنگاری هرگز KMS شما را ترک نمی‌کنند. Stirling فقط ciphertext را ذخیره و پردازش می‌کند که می‌توانید هر زمان لغو کنید." -title = "Stirling نمی‌تواند اسناد شما را رمزگشایی کند" - -[portal.infrastructure.security.ipAllowlist] -empty = "هیچ محدوده IP پیکربندی نشده است — همه IPها مجاز هستند." -heading = "IP allowlist" -sub = "دسترسی API به این محدوده‌های CIDR محدود شده است." -subLocked = "محدود کردن دسترسی API به محدوده‌های IP شناخته‌شده — در طرح‌های پولی در دسترس است." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "برای محدود کردن دسترسی API به شبکه‌های مشخص، به Pro ارتقا دهید." -title = "IP allowlisting یک قابلیت پولی است" - -[portal.infrastructure.security.ipColumns] -added = "افزوده‌شده" -addedBy = "افزوده‌شده توسط" -cidr = "CIDR" -label = "برچسب" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "مدیریت کلید رمزنگاری" -keyId = "شناسه کلید" -lastRotated = "آخرین چرخش" -rotateKey = "چرخش کلید" -rotationPolicy = "سیاست چرخش" -subheading = "نگه‌داری کلیدهایی که اسناد at rest را رمزنگاری می‌کنند — چه کسی می‌تواند رمزگشایی کند و کلیدها چگونه می‌چرخند." - -[portal.infrastructure.security.managedBanner] -description = "نگه‌داری bring-your-own-key (BYOK) و hold-your-own-key (HYOK) در Enterprise در دسترس است. برای ارائه کلیدها از KMS خودتان ارتقا دهید." -title = "در طرح شما، کلیدها توسط Stirling مدیریت می‌شوند" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "آسیا-اقیانوسیه" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · مرز داده GDPR" -label = "اتحادیه اروپا" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "ایالات متحده" - -[portal.infrastructure.security.residencyHeader] -heading = "Data residency" -subheading = "محل ذخیره و پردازش اسناد." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} استفاده‌شده" - -[portal.infrastructure.storage.empty] -description = "ذخیره‌سازی متصل و مصرف اینجا نمایش داده می‌شود." -title = "هیچ ذخیره‌سازی پیکربندی نشده است" - -[portal.infrastructure.storage.lifecycle] -active = "فعال" -activeRange = "0–{{value}} روز" -archived = "آرشیوشده" -coldStorage = "ذخیره‌سازی سرد" -deleted = "حذف‌شده" -never = "هرگز" -purged = "پاک‌سازی‌شده" - -[portal.infrastructure.storage.providers] -connect = "اتصال" -connected = "متصل" -heading = "ارائه‌دهندگان متصل" -subheading = "محل نوشتن artifactهای پردازش‌شده." - -[portal.infrastructure.storage.retention] -heading = "نگه‌داری" -subheading = "مدت نگه‌داری artifactها پیش از حذف چرخه عمر." -windowLabel = "بازه نگه‌داری پیش‌فرض" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} روز" -days_other = "{{count}} روز" -never = "هرگز حذف نکن" - -[portal.infrastructure.storage.totalUsage] -heading = "مصرف کل" -progressLabel = "ذخیره‌سازی استفاده‌شده" -subheading = "ذخیره‌سازی مصرف‌شده در همه ارائه‌دهندگان متصل." - [portal.infrastructure.tabs] apiKeys = "کلیدهای API" audit = "گزارش‌های حسابرسی" @@ -7715,11 +7657,9 @@ security = "امنیت" storage = "ذخیره‌سازی" [portal.integrations] -addAnother = "افزودن مورد دیگر" availableHeading = "در دسترس" comingSoonHeading = "به‌زودی" connect = "متصل کردن" -connectedHeading = "متصل‌شده" connectionCount_one = "{{count}} اتصال" connectionCount_other = "{{count}} اتصال" customApi = "API سفارشی" @@ -7737,6 +7677,10 @@ security = "امنیت" signing = "امضا" storage = "ذخیره‌سازی" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "متصل‌شده" @@ -7767,7 +7711,6 @@ integrations = "یکپارچه‌سازی‌ها" pipelines = "پایپ‌لاین‌ها" policies = "سیاست‌ها" procurement = "تدارکات" -settings = "تنظیمات" sources = "منابع" usage = "مصرف و صورت‌حساب" users = "کاربران" @@ -7784,7 +7727,7 @@ title = "پایپ‌لاین‌ها" newPipeline = "پایپ‌لاین جدید" [portal.pipelines.builder] -addStep = "افزودن ابزار" +activate = "Activate" back = "بازگشت به پایپ‌لاین‌ها" cannotFollow = "نمی‌تواند {{produced}} را بگیرد" chooseAccount = "یک حساب انتخاب کنید" @@ -7792,28 +7735,38 @@ chooseDestination = "یک مقصد انتخاب کنید" chooseOperation = "انتخاب کنید این مرحله چه کاری انجام دهد" chooseSource = "یک منبع انتخاب کنید" discard = "دور انداختن تغییرات" -enabled = "فعال" inputs = "ورودی" inputSource = "منبع ورودی" inputTrigger = "محرک" keepEditing = "ادامه ویرایش" +moreActions = "More actions" needsConfiguring = "نیاز به راه‌اندازی دارد" -needsUpload = "به یک فایل آپلودشده نیاز دارد" -noSources = "هنوز هیچ منبعی متصل نشده است. یکی بسازید تا از آن به‌عنوان ورودی استفاده کنید." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "هیچ ابزاری با جستجوی شما مطابقت ندارد." -pipelineSettings = "تنظیمات پایپ‌لاین" +pause = "Pause" +rename = "Rename pipeline" searchTools = "جستجوی ابزارها" -selectToolBody = "برای ساخت پایپ‌لاین خود، یک ابزار اضافه کنید." -selectToolTitle = "هنوز ابزاری وجود ندارد" sendToSystem = "ارسال به سیستم دیگر" stepsIncompatible = "این مراحل نمی‌توانند روی خروجی مرحله قبلی خود اجرا شوند: {{tools}}." stepsNeedSetup = "این مراحل پیش از ذخیره هنوز به راه‌اندازی نیاز دارند: {{tools}}." -toolSettings = "تنظیمات ابزار" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "عملیات ناشناخته، بدون تغییر نگه داشته شد." unsavedBody = "تغییرات ذخیره‌نشده دارید. پیش از خروج آن‌ها را ذخیره می‌کنید یا دور می‌اندازید؟" unsavedTitle = "تغییرات ذخیره‌نشده" -uploadUnsupported = "فایل‌های آپلودشده هنوز در پایپ‌لاین‌ها پشتیبانی نمی‌شوند، بنابراین این مراحل قابل ذخیره نیستند: {{tools}}." usesDefaults = "با تنظیمات پیش‌فرض اجرا می‌شود" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "همه فایل‌های مرحله قبلی را ترکیب می‌کند" @@ -7825,31 +7778,36 @@ undeclared-operation = "نمی‌توان بررسی کرد این مرحله چ [portal.pipelines.composer] addTool = "افزودن ابزار" -cancel = "لغو" -chainEmpty = "برای شروع ساخت پایپ‌لاین خود، یک ابزار اضافه کنید." create = "ایجاد پایپ‌لاین" +createPaused = "Create paused" editingUnsupported = "نمایش این params ابزار برای ویرایش هنوز پشتیبانی نمی‌شود." -moveDown = "انتقال به پایین" -moveUp = "انتقال به بالا" +editSource = "Edit source" name = "نام" namePlaceholder = "مثلاً Redaction sweep" noToolSettings = "این ابزار تنظیمات قابل پیکربندی ندارد." -operations_one = "عملیات ({{count}})" -operations_other = "عملیات ({{count}})" output = "خروجی" -removeStep = "حذف عملیات" save = "ذخیره تغییرات" scheduleEvery = "اجرا هر" -sources = "منابع" -sourcesLoading = "در حال بارگذاری منابع..." trigger = "Trigger" triggerManual = "فقط دستی" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "روز" hours = "ساعت" minutes = "دقیقه" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "«{{name}}» حذف شود؟ این کار قابل بازگشت نیست." cancel = "لغو" @@ -7867,6 +7825,37 @@ connectSource = "اتصال یک منبع" description = "اولین پایپ‌لاین خود را ایجاد کنید: منابعی را که روی آن‌ها اجرا می‌شود انتخاب کنید، عملیات را زنجیره کنید و محل خروجی را انتخاب کنید." title = "هنوز پایپ‌لاینی وجود ندارد" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "فعال" paused = "متوقف‌شده" @@ -7879,7 +7868,6 @@ completed_one = "اجرا کامل شد." completed_other = "همه {{count}} اجرا کامل شد." empty = "چیزی برای اجرا نیست: منابع سندی برای پردازش نداشتند." failed = "اجرا ناموفق بود: {{error}}" -historyCleared = "تاریخچه پاک شد. اجرای بعدی همه موارد فعلی در منابع را دوباره پردازش می‌کند." inFlight = "مورد جدیدی برای اجرا نیست: اسناد هنوز از اجرای قبلی در حال پردازش هستند." parked_one = "چیزی برای اجرا نیست: {{count}} سند قبلاً ناموفق بوده و کنار گذاشته شده است. علت را رفع کنید، سپس برای تلاش مجدد تاریخچه را پاک کنید." parked_other = "چیزی برای اجرا نیست: {{count}} سند قبلاً ناموفق بوده‌اند و کنار گذاشته شده‌اند. علت را رفع کنید، سپس برای تلاش مجدد تاریخچه را پاک کنید." @@ -7895,6 +7883,7 @@ name = "پایپ‌لاین" sources = "منابع" status = "وضعیت" steps = "مراحل" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "پایش پوشه" @@ -8209,6 +8198,24 @@ label = "پیوست به یک issue در Jira" description = "سند پردازش‌شده را به‌عنوان پیوست ارسال می‌کند." label = "ایمیل کردن سند (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "سند پردازش‌شده را داخل یک پوشه می‌نویسد." label = "آپلود به Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "به سقف رسیده" subtitle = "دوره آزمایشی رایگان شما تا {{date}} ادامه دارد. کارت لازم نیست." title = "دوره آزمایشی Enterprise" -[portal.search] -ariaLabel = "جستجو" -placeholder = "جستجو در Stirling — endpoints، پایپ‌لاین‌ها، مستندات…" - -[portal.search.empty] -noActionsDescription = "وقتی اقدامات سریع در دسترس باشند، اینجا نمایش داده می‌شوند." -noActionsTitle = "اقدام سریعی وجود ندارد" -noMatches = "هیچ نتیجه‌ای برای «{{query}}» یافت نشد" -noMatchesDescription = "یک کلیدواژه دیگر امتحان کنید یا کاتالوگ را مرور کنید." - [portal.settings.groups] admin = "مدیر" @@ -8743,10 +8740,6 @@ cancel = "لغو" confirm = "حذف" title = "منبع حذف شود؟" -[portal.sources.empty] -description = "یک پوشه (و به‌زودی فضای ذخیره‌سازی ابری) را متصل کنید تا خط‌مشی‌های شما جایی برای دریافت اسناد داشته باشند." -title = "هنوز هیچ منبعی متصل نشده است" - [portal.sources.kpi] inUse = "در حال استفاده" total = "اتصالات" @@ -8785,6 +8778,7 @@ unused = "استفاده‌نشده" documents = "اسناد" source = "منبع" status = "وضعیت" +type = "Type" usedBy = "خط‌مشی‌ها" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "سیاست‌ها و حریم خصوصی" [settings.preferences] title = "ترجیحات" -[settings.search] -placeholder = "جستجوی صفحات تنظیمات..." - [settings.security] description = "رمز عبور خود را به‌روزرسانی کنید تا حسابتان ایمن بماند." title = "امنیت" @@ -10128,6 +10119,7 @@ last = "صفحه آخر" maintainRatio = "تغییر حفظ نسبت ابعاد" next = "صفحه بعدی" noSavedSigs = "هیچ امضای ذخیره‌شده‌ای یافت نشد" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "امضاهای شخصی" previous = "صفحه قبلی" redo = "انجام مجدد" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "حذف پس‌زمینه ناموفق بود" hint = "یک تصویر PNG یا JPG از امضای خود بارگذاری کنید" label = "بارگذاری تصویر امضا" placeholder = "انتخاب فایل تصویر" +previewAlt = "Current image signature" processing = "در حال پردازش تصویر..." removeBackground = "حذف پس‌زمینه سفید (شفاف‌سازی)" @@ -10179,6 +10172,17 @@ saved = "یک امضای ذخیره‌شده را انتخاب کنید، سپس text = "پس از وارد کردن نام خود در بالا، هرجای PDF کلیک کنید تا امضای شما قرار گیرد." title = "چگونه امضا اضافه کنیم" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "جابجایی امضا" pause = "مکث جانمایی" @@ -10625,6 +10629,23 @@ title = "بارگذاری در سرور" updateButton = "به‌روزرسانی روی سرور" uploadButton = "بارگذاری در سرور" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "بازگشت" pdfTools = "ابزارهای PDF" placeholder = "برای شروع یک ابزار انتخاب کنید" premiumFeature = "ویژگی پرمیوم:" -searchTools = "جستجوی ابزارها" toolsHeader = "ابزارها" viewAllTools = "مشاهدهٔ همهٔ ابزارها" @@ -10822,6 +10842,9 @@ removal = "حذف" signing = "امضا" verification = "تأیید" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "هیچ ابزاری یافت نشد" noTools = "هیچ ابزاری موجود نیست" @@ -10979,6 +11002,13 @@ approver = "تأیید خط‌مشی" editor = "ویرایشگر" processor = "پردازشگر" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "دعوت به {{email}} لغو شود؟ آن‌ها دیگر نمی‌توانند با لینک فعلی بپیوندند." cancelInviteTitle = "لغو دعوت" @@ -10998,10 +11028,8 @@ title = "هنوز عضوی وجود ندارد" addToTeam = "افزودن به تیم" guestCount = "{{count}} مهمان" guests = "مهمانان" -guestsDesc = "همکاران خارجی، محدود به آنچه به اشتراک گذاشته‌اید. فقط ویرایشگر." ledBy = "به رهبری {{owner}}" org = "سازمان" -orgDesc = "مالکانی با اختیار در سراسر سازمان و تأیید خط‌مشی" owners = "{{count}} مالک" team = "تیم {{name}}" teamMeta = "{{count}} نفر" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "دعوت‌شده توسط {{who}}" cancel = "لغو" -count = "{{count}} در انتظار" -desc = "افراد دعوت‌شده‌ای که هنوز نپیوسته‌اند. تا زمانی که دعوت را بپذیرند، یک جایگاه را اشغال می‌کنند." expiresInDays_one = "{{count}} روز دیگر منقضی می‌شود" expiresInDays_other = "{{count}} روز دیگر منقضی می‌شود" expiresToday = "امروز منقضی می‌شود" title = "دعوت‌های در انتظار" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "در برقراری ارتباط با بک‌اند مشکلی پیش آمد، یا شما دسترسی ندارید. دوباره تلاش کنید." title = "امکان بارگیری اعضا نبود" @@ -11237,12 +11267,15 @@ searchPlaceholder = "جستجوی پیوست‌ها" title = "پیوست‌ها" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "عنوان نشانک" bookmarkTitleRequired = "عنوان نشانک الزامی است" closeSidebar = "بستن نوار کناری نشانک‌ها" collapseAll = "جمع کردن همه نشانک‌ها" +empty = "No bookmarks in this document" expandAll = "باز کردن همه نشانک‌ها" searchPlaceholder = "جستجوی نشانک‌ها" +title = "Bookmarks" [viewer.comments] addComment = "افزودن نظر" @@ -11264,10 +11297,12 @@ locateAnnotation = "یافتن در سند" moreActions = "عملیات بیشتر" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "صفحه {{page}}" placingHint = "برای قرار دادن، روی یک صفحه کلیک کنید… (لغو)" removeCommentOnly = "فقط حذف نظر" saveReply = "ذخیره پاسخ" +searchPlaceholder = "Search comments" title = "نظرات" typeComment = "نظر" typeInsertText = "درج متن" @@ -11290,6 +11325,7 @@ unsavedDesc = "تغییرات ذخیره‌نشده دارید" closeSidebar = "بستن نوار کناری لایه‌ها" hideAll = "پنهان کردن همه لایه‌ها" showAll = "نمایش همه لایه‌ها" +title = "Layers" [viewer.link] delete = "حذف لینک" @@ -11646,6 +11682,7 @@ exitRedaction = "خروج از حالت پنهان‌سازی" exportAll = "خروجی گرفتن از PDF" exportSelected = "خروجی گرفتن از صفحات منتخب" formFill = "پر کردن فرم" +hideToolbar = "Hide toolbar" multiTool = "ابزار چندگانه" panMode = "حالت جابه‌جایی" print = "چاپ PDF" @@ -11666,6 +11703,7 @@ selectAll = "انتخاب همه" selectByNumber = "انتخاب بر اساس شماره‌های صفحه" selectLanguage = "انتخاب زبان" share = "اشتراک‌گذاری" +showToolbar = "Show toolbar" toggleAnnotations = "نمایش/مخفی کردن حاشیه‌نویسی‌ها" toggleAttachments = "نمایش/مخفی کردن پیوست‌ها" toggleBookmarks = "نمایش/مخفی کردن نشانک‌ها" diff --git a/frontend/editor/public/locales/fr-FR/translation.toml b/frontend/editor/public/locales/fr-FR/translation.toml index 9c38d2209a..8734944054 100644 --- a/frontend/editor/public/locales/fr-FR/translation.toml +++ b/frontend/editor/public/locales/fr-FR/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alphabet" apply = "Appliquer" applyAndContinue = "Appliquer et continuer" -areYouSure = "Voulez-vous vraiment quitter ?" back = "Retour" black = "Noir" blue = "Bleu" @@ -106,6 +105,7 @@ undoSuccess = "Opération annulée avec succès" unknown = "Inconnu" unpin = "Désépingler le fichier (remplacer après l’exécution de l’outil)" unsavedChanges = "Vous avez des modifications non enregistrées sur votre PDF. Que souhaitez-vous faire ?" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Modifications non enregistrées" unsupported = "Non pris en charge" username = "Nom d'utilisateur" @@ -1879,6 +1879,9 @@ width = "Largeur" [app] description = "L’alternative gratuite à Adobe Acrobat (10M+ téléchargements)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Convertir en PDF/A-3b" convertToPdfA3bDescription = "Crée un PDF d’archivage avec pièces jointes intégrées" @@ -2933,12 +2936,16 @@ text = "Pour rendre ces permissions non modifiables, utilisez l’outil Ajouter [chat.actions] copy = "Copier le message" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Options de l’agent Stirling" +assistant = "AI Assistant" clearChat = "Effacer la discussion" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Cette opération utilisera vos crédits cloud" tooltip = "Prélever une couleur à l’écran" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Choisir la couleur" [common] +actions = "Actions" back = "Retour" cancel = "Annuler" close = "Fermer" +codeSample = "Code sample" collapse = "Réduire" confirm = "Confirmer" continue = "Continuer" copied = "Copié !" copy = "Copier" done = "Terminé" +enterValue = "Enter value" error = "Erreur" expand = "Développer" loading = "Chargement…" next = "Suivant" +open = "Open" preview = "Aperçu" previous = "Précédent" refresh = "Actualiser" remaining = "Restant" retry = "Réessayer" save = "Enregistrer" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Effacer la sélection" @@ -3185,6 +3199,7 @@ title = "Méthode de compression" [compress.settings] desiredSize = "Taille de fichier souhaitée" desiredSizePlaceholder = "Saisir la taille" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "La compression est un moyen simple de réduire la taille de votre fichier. Choisissez Taille de fichier pour saisir une taille cible et nous ajusterons la qualité pour vous. Choisissez Qualité pour régler manuellement la force de compression." @@ -3387,6 +3402,24 @@ pdfOptions = "Options PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Sélectionnez d’abord un format source" settings = "Paramètres" single = "Unique" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Rechercher..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Modifier" @@ -3824,11 +3861,13 @@ shareSelected = "Partager la sélection" sharing = "Partage" showAll = "Tout afficher" showHistory = "Afficher l’historique" +sortBy = "Sort files" sortByDate = "Trier par date" sortByName = "Trier par nom" sortBySize = "Trier par taille" storage = "Stockage" storageState = "Stockage" +storageUsed = "Storage used" synced = "Synchronisé" title = "Téléverser des fichiers PDF" toolChain = "Outils appliqués" @@ -3874,8 +3913,12 @@ addFiles = "Ajouter des fichiers" addingFiles = "Ajout de fichiers…" collapse = "Réduire la barre latérale" customizeGroups = "Personnaliser les groupes" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Ouvrez des fichiers pour commencer" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Développer la barre latérale" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive n’est pas configuré" @@ -3888,15 +3931,17 @@ openFromComputer = "Ouvrir depuis l’ordinateur" openSettings = "Ouvrir les paramètres" other = "Autre" recent = "Récent" -search = "Rechercher" -searchPlaceholder = "Rechercher des fichiers..." viewAll = "Voir les {{count}} fichiers" [fileSidebar.fileItem] closeViewer = "Fermer la visionneuse" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Ouvrir dans la visionneuse" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Réinitialiser aux valeurs par défaut" subtitle = "Regroupez vos fichiers dans des catégories parentes. Ajoutez des étiquettes existantes ou nouvelles à une catégorie, renommez-la ou créez la vôtre. Les fichiers qui ne figurent dans aucune de vos catégories apparaissent sous « Autre »." title = "Catégories de la barre latérale" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Ajouter à l’espace de travail" addToWorkspaceCount = "Ajouter {{count}} à l’espace de travail" @@ -3918,7 +3971,6 @@ backToMyFiles = "Retour à Mes fichiers" breadcrumbs = "Chemin du dossier" cancel = "Annuler" classification = "Classification" -clearSearch = "Effacer la recherche" clearSelection = "Effacer la sélection" closeDetails = "Fermer les détails" create = "Créer" @@ -3950,6 +4002,7 @@ downloadAll = "Tout télécharger" downloadVersion = "Télécharger cette version" dropOverlay = "Déposez les fichiers pour téléverser" dropOverlaySub = "Les fichiers commencent en Local. Utilisez « Déplacer vers » ou « Enregistrer dans le cloud » pour les organiser dans un dossier." +duplicate = "Duplicate" file = "Fichier" fileInfo = "File info" fileMenu = "Actions sur le fichier" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Redimensionner l’arborescence des dossiers (touches fléch save = "Enregistrer" saveToServer = "Enregistrer sur le serveur" saveToServerDisabledHint = "L’enregistrement sur le serveur n’est pas activé sur ce serveur. Demandez à votre administrateur de l’activer." -search = "Rechercher" -searchPlaceholder = "Rechercher dans ce dossier et les sous-dossiers" selectAll = "Tout sélectionner" selectAllHint = "Cliquez pour tout sélectionner. Astuce : maintenez Ctrl (ou Cmd) pour ajouter des fichiers un par un, Maj pour sélectionner une plage." selectedCount = "{{count}} sélectionné(s)" @@ -4044,6 +4095,10 @@ title = "Pas encore de fichiers cloud" hint = "Les fichiers enregistrés sans téléversement restent ici. Déposez un fichier pour en ajouter un." title = "Aucun fichier local uniquement" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Les fichiers que vous ouvrez ou modifiez apparaîtront ici." title = "Aucune modification pour l’instant" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Impossible de supprimer le dossier." deleteFolderFailedDetail = "Impossible de supprimer le dossier : {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Impossible de mettre à jour l’apparence du dossier." folderAppearanceFailedDetail = "Impossible de mettre à jour l’apparence du dossier : {{message}}" moveFilesFailed = "Impossible de déplacer les fichiers." @@ -4113,6 +4170,11 @@ localHint = "Stocké uniquement dans ce navigateur" shared = "Partagé" sharedHint = "Partagé avec vous via un lien" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Plus ancien d’abord" modifiedDesc = "Plus récent d’abord" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Aucun fichier disponible dans le stockage. Ouvrez d’ab open = "Ouvrir" openFile = "Ouvrir un fichier" openFiles = "Ouvrir des fichiers" +selectFile = "Select {{name}}" selectFromStorage = "Sélectionner depuis le stockage" upload = "Téléverser" uploadFile = "Téléverser un fichier" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Tout est enregistré" +analyzingFields = "Analysing form fields..." extractCsvError = "Échec de l’extraction du CSV" extractXlsxError = "Échec de l’extraction du XLSX" +filled = "filled" flattenAfterFilling = "Aplatir après remplissage" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obl." requiredFieldsError = "Veuillez remplir tous les champs obligatoires" rescanFields = "Réanalyser les champs" @@ -4795,9 +4863,6 @@ title = "Image en PDF" [imageToPdf] tags = "pdf,conversion,img,jpg,image,photo" -[infoBanner] -dismiss = "Fermer" - [invite] acceptError = "Échec de la création du compte" accountFor = "Création du compte pour" @@ -4979,6 +5044,34 @@ uploadSuccess = "Téléversement réussi !" uploadSuccessMessage = "Vos images ont été transférées." validating = "Validation de la session..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Scannez pour téléverser des photos. Les images se convertissent automatiquement en PDF." descriptionNoConvert = "Scannez pour téléverser des photos depuis votre appareil mobile." @@ -4997,8 +5090,13 @@ title = "Téléverser depuis un mobile" tags = "outil multifonction,opération multifonction,interface utilisateur,glisser déposer,front-end,client side,interactif,intransigeant,déplacer,multi tool" title = "Outil multifonction PDF" -[navbar] -search = "Rechercher" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "L'authentification n'a pas abouti. Vous pouvez fermer cette fenêtre et réessayer." @@ -5127,6 +5225,7 @@ activeFiles = "La vue Fichiers actifs affiche tous les PDF char allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Fermer" cropSettings = "Maintenant que nous avons sélectionné le fichier que nous voulons rogner, nous pouvons configurer l’outil Rogner pour définir la zone de rognage du PDF." +dialogLabel = "Onboarding" fileCheckbox = "Cliquer sur l’un des fichiers le sélectionne pour le traitement. Vous pouvez en sélectionner plusieurs pour des opérations par lots." fileReplacement = "Le fichier modifié remplacera automatiquement le fichier d’origine dans le Workbench, ce qui vous permettra de le faire passer facilement par d’autres outils." filesButton = "Le bouton Fichiers de la barre d’accès rapide vous permet de téléverser des PDF à utiliser avec les outils." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF en PDF/A" tags = "convertion,archive,long-term,standard,conversion,storage,préservation,preservation" title = "PDF en PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "impression,standard,conversion,production,prépresse,archive" title = "PDF vers PDF/X" @@ -6045,6 +6151,7 @@ small = "500 Credits" xsmall = "100 Credits" [plan.availablePlans] +currency = "Billing currency" subtitle = "Choisissez le forfait adapté à vos besoins" title = "Forfaits disponibles" @@ -6624,6 +6731,7 @@ reachedTitle = "Limite de dépenses mensuelle atteinte" title = "Impossible d’ouvrir le portail Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "sur {{allowance}} PDF gratuits utilisés" capSuffix_other = "sur {{allowance}} PDF gratuits utilisés" eyebrow = "Essai Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Envoyer un document ou une notification par email lorsqu’une politique s’exécute." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Classer les documents traités dans Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDK officiels" beta = "Beta" deprecated = "Obsolète" -[portal.docs.search] -empty = "Aucune doc correspondante" -placeholder = "Rechercher dans les docs" -results = "{{count}} résultats" - [portal.docs.skills] eyebrow = "COMPÉTENCES" lead = "Capacités nommées et groupées que votre agent invoque comme un seul outil. Chaque compétence est une chaîne d’opérations déterministe avec des évaluations associées." @@ -7180,11 +7309,11 @@ editorAction = "Éditeur" empty = "Aucun document ne correspond à ce filtre." rowActions = "Actions de ligne" sensitiveLabel = "Sensible" -sensitiveTitle = "Sensible — accès requis" [portal.documents.table.columns] action = "Pipeline / Action" document = "Document" +labels = "Labels" product = "Produit" status = "Statut" time = "Heure" @@ -7200,6 +7329,7 @@ host = "Hôte" lastSeen = "Dernière activité" region = "Région" status = "Statut" +target = "Target" version = "Version" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Cette vue a rencontré une erreur inattendue. Réessayez ou chois retry = "Réessayer" title = "Un problème est survenu sur cette page" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Retour" body = "Téléchargez l’application de bureau ou auto-hébergez le serveur." @@ -7380,11 +7560,6 @@ cancel = "Annuler" confirm = "Révoquer la clé" title = "Révoquer la clé API" -[portal.infrastructure.attestationLabel] -attested = "Attesté" -inScope = "Dans le périmètre" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtrer les événements d’audit par catégorie" heading = "Journaux d’audit" @@ -7456,11 +7631,6 @@ info = "Info" success = "Succès" warning = "Avertissement" -[portal.infrastructure.certLabel] -certified = "Certifié" -inProgress = "En cours" -notStarted = "Non démarré" - [portal.infrastructure.createKey] cancel = "Annuler" createKey = "Créer une clé" @@ -7474,238 +7644,10 @@ subtitleCreated = "Copiez ce secret maintenant — il ne sera plus affiché." title = "Créer une clé API" titleCreated = "Clé créée" -[portal.infrastructure.deployLabel] -live = "En ligne" -queued = "En file d’attente" -rolledBack = "Restauré" -rolling = "Déploiement en cours" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Déployé par" -environment = "Environnement" -product = "Produit" -status = "Statut" -version = "Version" -when = "Quand" - -[portal.infrastructure.deployments.recent] -heading = "Déploiements récents" -subheading = "Les derniers déploiements sur les produits et environnements." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instances" -latency = "Latence" -load = "Charge" -p99 = "P99" -region = "Région" -status = "Statut" -throughput = "Débit" -uptime = "Temps de disponibilité" -version = "Version" - -[portal.infrastructure.deployments.regions] -heading = "Régions" -subheading = "Santé en direct de chaque région Stirling déployée — latence, charge et version de déploiement." - -[portal.infrastructure.deployments.regions.empty] -description = "Les régions déployées apparaissent ici une fois votre espace de travail provisionné." -title = "Aucune région déployée" - [portal.infrastructure.keyLabel] active = "Active" revoked = "Révoquée" -[portal.infrastructure.modelLabel] -active = "Actif" -degraded = "Dégradé" -disabled = "Désactivé" - -[portal.infrastructure.models] -heading = "Modèles" -msValue = "{{value}} ms" -subheading = "Le catalogue de modèles et le routage qui alimentent le traitement des documents dans votre espace de travail." - -[portal.infrastructure.models.byom] -description = "Enregistrez un modèle on-prem ou auto-hébergé et épinglez-le à une région pour un traitement lié à la résidence des données." -title = "Apportez votre propre modèle" - -[portal.infrastructure.models.catalogue] -heading = "Catalogue" -sub = "Modèles gérés disponibles pour votre espace de travail, avec latence et coût en direct." -subEnterprise = "Modèles gérés, apportez-votre-propre-modèle et on-prem — avec épinglage par région disponible." - -[portal.infrastructure.models.catalogue.empty] -description = "Les modèles du catalogue de votre espace de travail apparaissent ici." -title = "Aucun modèle disponible" - -[portal.infrastructure.models.columns] -cost = "Coût" -latency = "Latence" -load = "Charge" -model = "Modèle" -status = "Statut" -type = "Type" -version = "Version" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/appel" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Modèles actifs" -avgLatency = "Latence moy." -included = "Inclus" -monthlySpend = "Dépense mensuelle des modèles" - -[portal.infrastructure.models.routing] -empty = "Aucune règle de routage configurée." -heading = "Règles de routage" -sub = "Quel modèle gère chaque opération. La valeur par défaut s’applique lorsqu’aucune règle plus précise ne correspond." -subLocked = "Acheminez les opérations vers des modèles spécifiques — disponible sur les plans payants." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Passez à Pro pour contrôler quel modèle gère chaque opération et type de document." -title = "Le routage des modèles est une fonctionnalité payante" - -[portal.infrastructure.models.routingColumns] -default = "Par défaut" -docType = "Type de document" -modelForAria = "Modèle pour {{operation}}" -operation = "Opération" -routedTo = "Acheminé vers" - -[portal.infrastructure.modelTypeLabel] -classification = "Classification" -extraction = "Extraction" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Dégradé" -down = "Indisponible" -healthy = "Sain" - -[portal.infrastructure.security.access.byok] -description = "Fournissez une clé depuis votre propre KMS. Stirling chiffre avec celle-ci mais peut toujours lire." -label = "Apportez votre propre clé (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Les clés ne quittent jamais votre KMS. Stirling ne détient que du texte chiffré." -label = "Conservez votre propre clé (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling gère les clés de chiffrement. Le plus simple — aucune opération de clé de votre côté." -label = "Clés détenues par Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Politique d’accès aux documents" -subheading = "Contrôle qui peut déchiffrer les documents traités au repos." - -[portal.infrastructure.security.attestations] -heading = "Attestations de conformité" -noReport = "Aucun rapport disponible" -subheading = "Posture d’audit cadre par cadre, avec rapports disponibles sur les contrôles attestés." -viewReport = "Voir le rapport →" - -[portal.infrastructure.security.compliance] -heading = "Conformité" -subheading = "Attestations et certifications couvrant la plateforme Stirling." - -[portal.infrastructure.security.empty] -description = "La configuration de sécurité de votre espace de travail apparaîtra ici." -title = "Posture de sécurité indisponible" - -[portal.infrastructure.security.hyokBanner] -description = "Avec HYOK, les clés de chiffrement ne quittent jamais votre KMS. Stirling stocke et traite uniquement du texte chiffré que vous pouvez révoquer à tout moment." -title = "Stirling ne peut pas déchiffrer vos documents" - -[portal.infrastructure.security.ipAllowlist] -empty = "Aucune plage IP configurée — toutes les IP sont autorisées." -heading = "Liste d’autorisation IP" -sub = "L’accès API est limité à ces plages CIDR." -subLocked = "Limitez l’accès API aux plages IP connues — disponible sur les plans payants." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Passez à Pro pour limiter l’accès API à des réseaux spécifiques." -title = "La liste d’autorisation IP est une fonctionnalité payante" - -[portal.infrastructure.security.ipColumns] -added = "Ajouté" -addedBy = "Ajouté par" -cidr = "CIDR" -label = "Libellé" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithme" -heading = "Gestion des clés de chiffrement" -keyId = "Identifiant de clé" -lastRotated = "Dernière rotation" -rotateKey = "Faire tourner la clé" -rotationPolicy = "Politique de rotation" -subheading = "Garde des clés qui chiffrent les documents au repos — qui peut déchiffrer et comment les clés tournent." - -[portal.infrastructure.security.managedBanner] -description = "La garde apportez-votre-propre-clé (BYOK) et conservez-votre-propre-clé (HYOK) est disponible sur Enterprise. Mettez à niveau pour fournir des clés depuis votre propre KMS." -title = "Les clés sont gérées par Stirling avec votre plan" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asie-Pacifique" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · limite des données GDPR" -label = "Union européenne" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "États-Unis" - -[portal.infrastructure.security.residencyHeader] -heading = "Résidence des données" -subheading = "Où les documents sont stockés et traités." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} utilisé" - -[portal.infrastructure.storage.empty] -description = "Le stockage connecté et l’utilisation apparaissent ici." -title = "Aucun stockage configuré" - -[portal.infrastructure.storage.lifecycle] -active = "Actif" -activeRange = "0–{{value}}j" -archived = "Archivé" -coldStorage = "stockage froid" -deleted = "Supprimé" -never = "jamais" -purged = "purgé" - -[portal.infrastructure.storage.providers] -connect = "Connecter" -connected = "Connecté" -heading = "Fournisseurs connectés" -subheading = "Où les artefacts traités sont écrits." - -[portal.infrastructure.storage.retention] -heading = "Rétention" -subheading = "Durée de conservation des artefacts avant suppression du cycle de vie." -windowLabel = "Fenêtre de rétention par défaut" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} jour" -days_other = "{{count}} jours" -never = "Ne jamais supprimer" - -[portal.infrastructure.storage.totalUsage] -heading = "Utilisation totale" -progressLabel = "Stockage utilisé" -subheading = "Stockage consommé sur tous les fournisseurs connectés." - [portal.infrastructure.tabs] apiKeys = "Clés API" audit = "Journaux d’audit" @@ -7715,11 +7657,9 @@ security = "Sécurité" storage = "Stockage" [portal.integrations] -addAnother = "Ajouter un autre" availableHeading = "Disponible" comingSoonHeading = "Bientôt disponible" connect = "Connecter" -connectedHeading = "Connecté" connectionCount_one = "{{count}} connexion" connectionCount_other = "{{count}} connexions" customApi = "API personnalisée" @@ -7737,6 +7677,10 @@ security = "Sécurité" signing = "Signature" storage = "Stockage" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Connecté" @@ -7767,7 +7711,6 @@ integrations = "Intégrations" pipelines = "Pipelines" policies = "Politiques" procurement = "Achats" -settings = "Paramètres" sources = "Sources" usage = "Utilisation et facturation" users = "Utilisateurs" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Nouveau pipeline" [portal.pipelines.builder] -addStep = "Ajouter un outil" +activate = "Activate" back = "Retour aux pipelines" cannotFollow = "Impossible d’accepter {{produced}}" chooseAccount = "Choisir un compte" @@ -7792,28 +7735,38 @@ chooseDestination = "Choisir une destination" chooseOperation = "Choisir l’action de cette étape" chooseSource = "Choisir une source" discard = "Abandonner les modifications" -enabled = "Activé" inputs = "Entrée" inputSource = "Source d’entrée" inputTrigger = "Déclencheur" keepEditing = "Continuer à modifier" +moreActions = "More actions" needsConfiguring = "Configuration requise" -needsUpload = "Nécessite un fichier téléversé" -noSources = "Aucune source connectée pour le moment. Créez-en une pour l’utiliser comme entrée." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Aucun outil ne correspond à votre recherche." -pipelineSettings = "Paramètres du pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Rechercher des outils" -selectToolBody = "Ajoutez un outil pour construire votre pipeline." -selectToolTitle = "Aucun outil pour le moment" sendToSystem = "Envoyer vers un autre système" stepsIncompatible = "Ces étapes ne peuvent pas s’exécuter sur ce que produit leur étape précédente : {{tools}}." stepsNeedSetup = "Ces étapes doivent encore être configurées avant l’enregistrement : {{tools}}." -toolSettings = "Paramètres de l’outil" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Opération non reconnue, conservée telle quelle." unsavedBody = "Vous avez des modifications non enregistrées. Enregistrez-les avant de quitter ou abandonnez-les ?" unsavedTitle = "Modifications non enregistrées" -uploadUnsupported = "Les fichiers téléversés ne sont pas encore pris en charge dans les pipelines, ces étapes ne peuvent donc pas être enregistrées : {{tools}}." usesDefaults = "S’exécute avec les paramètres par défaut" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combine tous les fichiers de l’étape précédente" @@ -7825,31 +7778,36 @@ undeclared-operation = "Impossible de vérifier ce que cette étape accepte" [portal.pipelines.composer] addTool = "Ajouter un outil" -cancel = "Annuler" -chainEmpty = "Ajoutez un outil pour commencer à construire votre pipeline." create = "Créer le pipeline" +createPaused = "Create paused" editingUnsupported = "L’affichage de ces paramètres d’outil pour modification n’est pas encore pris en charge." -moveDown = "Déplacer vers le bas" -moveUp = "Déplacer vers le haut" +editSource = "Edit source" name = "Nom" namePlaceholder = "p. ex. Passage de caviardage" noToolSettings = "Cet outil n’a aucun paramètre configurable." -operations_one = "Opération ({{count}})" -operations_other = "Opérations ({{count}})" output = "Sortie" -removeStep = "Supprimer l’opération" save = "Enregistrer les modifications" scheduleEvery = "Exécuter tous les" -sources = "Sources" -sourcesLoading = "Chargement des sources..." trigger = "Déclencheur" triggerManual = "Manuel uniquement" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "jours" hours = "heures" minutes = "minutes" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Supprimer \"{{name}}\" ? Cette action est irréversible." cancel = "Annuler" @@ -7867,6 +7825,37 @@ connectSource = "Connecter une source" description = "Créez votre premier pipeline : choisissez les sources sur lesquelles il s’exécute, enchaînez les opérations et choisissez où va la sortie." title = "Aucun pipeline pour le moment" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Actifs" paused = "En pause" @@ -7879,7 +7868,6 @@ completed_one = "Exécution terminée." completed_other = "Les {{count}} exécutions sont terminées." empty = "Rien à exécuter : les sources n’avaient aucun document à traiter." failed = "Échec de l’exécution : {{error}}" -historyCleared = "Historique effacé. La prochaine exécution retraitera tout ce qui se trouve actuellement dans les sources." inFlight = "Rien de nouveau à exécuter : des documents sont encore en cours de traitement depuis une exécution précédente." parked_one = "Rien à exécuter : {{count}} document a échoué précédemment et est mis en attente. Corrigez la cause, puis effacez l’historique pour le réessayer." parked_other = "Rien à exécuter : {{count}} documents ont échoué précédemment et sont mis en attente. Corrigez la cause, puis effacez l’historique pour les réessayer." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Sources" status = "Statut" steps = "Étapes" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Surveillance de dossier" @@ -8209,6 +8198,24 @@ label = "Joindre à un ticket Jira" description = "Envoie le document traité en pièce jointe." label = "Envoyer le document par e-mail (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Écrit le document traité dans un dossier." label = "Téléverser vers Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Limite atteinte" subtitle = "Votre essai gratuit est valable jusqu’au {{date}}. Aucune carte requise." title = "Essai Enterprise" -[portal.search] -ariaLabel = "Rechercher" -placeholder = "Rechercher dans Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Les actions rapides apparaîtront ici une fois disponibles." -noActionsTitle = "Aucune action rapide" -noMatches = "Aucune correspondance pour \"{{query}}\"" -noMatchesDescription = "Essayez un autre mot-clé ou parcourez le catalogue." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Annuler" confirm = "Supprimer" title = "Supprimer la source ?" -[portal.sources.empty] -description = "Connectez un dossier (et bientôt un stockage cloud) afin que vos politiques disposent d’un emplacement où récupérer des documents." -title = "Aucune source connectée pour le moment" - [portal.sources.kpi] inUse = "En cours d’utilisation" total = "Connexions" @@ -8785,6 +8778,7 @@ unused = "Inutilisé" documents = "Documents" source = "Source" status = "Statut" +type = "Type" usedBy = "Politiques" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Politiques et confidentialité" [settings.preferences] title = "Préférences" -[settings.search] -placeholder = "Rechercher dans les paramètres…" - [settings.security] description = "Mettez à jour votre mot de passe pour sécuriser votre compte." title = "Sécurité" @@ -10128,6 +10119,7 @@ last = "Dernière page" maintainRatio = "Conserver les proportions" next = "Page suivante" noSavedSigs = "Aucun sceau enregistré trouvé" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Sceaux personnels" previous = "Page précédente" redo = "Refaire" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Échec de la suppression du fond" hint = "Téléversez une image PNG ou JPG de votre signature" label = "Téléverser une image de signature" placeholder = "Sélectionner un fichier image" +previewAlt = "Current image signature" processing = "Traitement de l’image..." removeBackground = "Supprimer le fond blanc (le rendre transparent)" @@ -10179,6 +10172,17 @@ saved = "Sélectionnez une signature enregistrée ci-dessus, puis cliquez n’im text = "Après avoir saisi votre nom ci-dessus, cliquez n’importe où sur le PDF pour placer votre signature." title = "Comment ajouter une signature" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Déplacer la signature" pause = "Suspendre le placement" @@ -10625,6 +10629,23 @@ title = "Téléverser sur le serveur" updateButton = "Mettre à jour sur le serveur" uploadButton = "Téléverser sur le serveur" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Revenir en arrière" pdfTools = "Outils PDF" placeholder = "Choisissez un outil pour commencer" premiumFeature = "Fonctionnalité Premium :" -searchTools = "Rechercher des outils" toolsHeader = "Outils" viewAllTools = "Voir tous les outils" @@ -10822,6 +10842,9 @@ removal = "Suppression" signing = "Signature" verification = "Vérification" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Aucun outil trouvé" noTools = "Aucun outil disponible" @@ -10979,6 +11002,13 @@ approver = "Approuve la politique" editor = "Éditeur" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Annuler l’invitation envoyée à {{email}} ? Cette personne ne pourra pas rejoindre avec le lien actuel." cancelInviteTitle = "Annuler l’invitation" @@ -10998,10 +11028,8 @@ title = "Aucun membre pour le moment" addToTeam = "Ajouter à l’équipe" guestCount = "{{count}} invité" guests = "Invités" -guestsDesc = "Collaborateurs externes, limités à ce que vous avez partagé. Éditeur uniquement." ledBy = "dirigée par {{owner}}" org = "Organisation" -orgDesc = "Propriétaires avec autorité à l’échelle de l’organisation et approbation des politiques" owners = "{{count}} propriétaire" team = "Équipe {{name}}" teamMeta = "{{count}} personnes" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Invité par {{who}}" cancel = "Annuler" -count = "{{count}} en attente" -desc = "Personnes invitées qui n’ont pas encore rejoint. Elles occupent une place jusqu’à leur acceptation." expiresInDays_one = "Expire dans {{count}} jour" expiresInDays_other = "Expire dans {{count}} jours" expiresToday = "Expire aujourd’hui" title = "Invitations en attente" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Une erreur s’est produite lors de l’accès au backend, ou vous n’avez pas accès. Réessayez." title = "Impossible de charger les membres" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Rechercher des pièces jointes" title = "Pièces jointes" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Titre du signet" bookmarkTitleRequired = "Le titre du signet est requis" closeSidebar = "Fermer le panneau latéral des signets" collapseAll = "Réduire tous les signets" +empty = "No bookmarks in this document" expandAll = "Développer tous les signets" searchPlaceholder = "Rechercher des signets" +title = "Bookmarks" [viewer.comments] addComment = "Ajouter un commentaire" @@ -11264,10 +11297,12 @@ locateAnnotation = "Chercher dans le document" moreActions = "Plus d'actions" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Page {{page}}" placingHint = "Cliquez sur une page pour placer… (annuler)" removeCommentOnly = "Supprimer seulement le commentaire" saveReply = "Sauvegarder la réponse" +searchPlaceholder = "Search comments" title = "Commentaires" typeComment = "Commentaire" typeInsertText = "Insérer du text" @@ -11290,6 +11325,7 @@ unsavedDesc = "Vous avez des modifications non enregistrées" closeSidebar = "Fermer le panneau latéral des calques" hideAll = "Masquer tous les calques" showAll = "Afficher tous les calques" +title = "Layers" [viewer.link] delete = "Supprimer le lien" @@ -11646,6 +11682,7 @@ exitRedaction = "Quitter le mode Rédaction" exportAll = "Exporter le PDF" exportSelected = "Exporter les pages sélectionnées" formFill = "Remplir le formulaire" +hideToolbar = "Hide toolbar" multiTool = "Outil multifonction" panMode = "Mode panoramique" print = "Imprimer le PDF" @@ -11666,6 +11703,7 @@ selectAll = "Tout sélectionner" selectByNumber = "Sélectionner par numéros de pages" selectLanguage = "Sélectionner la langue" share = "Partager" +showToolbar = "Show toolbar" toggleAnnotations = "Basculer la visibilité des annotations" toggleAttachments = "Basculer les pièces jointes" toggleBookmarks = "Basculer les signets" diff --git a/frontend/editor/public/locales/ga-IE/translation.toml b/frontend/editor/public/locales/ga-IE/translation.toml index 8b6209d3d3..718b2c593f 100644 --- a/frontend/editor/public/locales/ga-IE/translation.toml +++ b/frontend/editor/public/locales/ga-IE/translation.toml @@ -1,7 +1,6 @@ alphabet = "Aibítir" apply = "Cuir i bhFeidhm" applyAndContinue = "Sábháil & Fág" -areYouSure = "An bhfuil tú cinnte gur mian leat imeacht?" back = "Siar" black = "Dubh" blue = "Gorm" @@ -106,6 +105,7 @@ undoSuccess = "Oibríocht cealaithe go rathúil" unknown = "Anaithnid" unpin = "Díphionnáil Comhad (cuir ina ionad tar éis rith na huirlise)" unsavedChanges = "Tá athruithe gan sábháil agat ar do PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Athruithe Gan Sábháil" unsupported = "Gan tacaíocht" username = "Ainm úsáideora" @@ -1879,6 +1879,9 @@ width = "Leithead" [app] description = "An rogha saor in aisce seachas Adobe Acrobat (10M+ íoslódálacha)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Tiontaigh go PDF/A-3b" convertToPdfA3bDescription = "Cruthaíonn sé PDF cartlannaithe le ceangaltáin leabaithe" @@ -2933,12 +2936,16 @@ text = "Chun na ceadanna seo a dhéanamh dodhéanta a athrú, úsáid an uirlis [chat.actions] copy = "Cóipeáil teachtaireacht" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Roghanna gníomhaire Stirling" +assistant = "AI Assistant" clearChat = "Glan comhrá" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Úsáidfidh an oibríocht seo do chreidmheasanna scamall" tooltip = "Roghnaigh dath ón scáileán" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Roghnaigh dath" [common] +actions = "Actions" back = "Siar" cancel = "Cealaigh" close = "Dún" +codeSample = "Code sample" collapse = "Fill" confirm = "Deimhnigh" continue = "Lean ar aghaidh" copied = "Cóipeáilte!" copy = "Cóipeáil" done = "Déanta" +enterValue = "Enter value" error = "Earráid" expand = "Leathnaigh" loading = "Á lódáil..." next = "Ar Aghaidh" +open = "Open" preview = "Réamhamharc" previous = "Roimhe Seo" refresh = "Athnuaigh" remaining = "Fágtha" retry = "Bain triail eile as" save = "Sábháil" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Glan roghnaithe" @@ -3185,6 +3199,7 @@ title = "Modh Comhbhrúite" [compress.settings] desiredSize = "Méid comhaid inmhianaithe" desiredSizePlaceholder = "Iontráil an méid" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Is bealach éasca é comhbhrú chun do mhéid comhaid a laghdú. Roghnaigh Méid Comhaid chun spriocmhéid a iontráil agus ligean dúinn an cháilíocht a choigeartú duitse. Roghnaigh Cáilíocht chun neart an chomhbhrú a shocrú de láimh." @@ -3387,6 +3402,24 @@ pdfOptions = "Roghanna PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Roghnaigh formáid foinse ar dtús" settings = "Socruithe" single = "Aonair" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Cuardaigh..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Cuir in eagar" @@ -3824,11 +3861,13 @@ shareSelected = "Comhroinn Roghnaithe" sharing = "Comhroinnt" showAll = "Taispeáin Uile" showHistory = "Taispeáin Stair" +sortBy = "Sort files" sortByDate = "Sórtáil de réir Dáta" sortByName = "Sórtáil de réir Ainm" sortBySize = "Sórtáil de réir Méid" storage = "Stóráil" storageState = "Stóráil" +storageUsed = "Storage used" synced = "Sioncrónaithe" title = "Uaslódáil Comhaid PDF" toolChain = "Uirlisí a Cuireadh i bhFeidhm" @@ -3874,8 +3913,12 @@ addFiles = "Cuir comhaid leis" addingFiles = "Comhaid á gcur leis…" collapse = "Fill an barra taoibh" customizeGroups = "Saincheap grúpaí" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Oscail comhaid chun tosú" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Leathnaigh an barra taoibh" googleDrive = "Google Drive" googleDriveDisabled = "Níl Google Drive cumraithe" @@ -3888,15 +3931,17 @@ openFromComputer = "Oscail ón ríomhaire" openSettings = "Oscail socruithe" other = "Eile" recent = "Le déanaí" -search = "Cuardaigh" -searchPlaceholder = "Cuardaigh comhaid..." viewAll = "Amharc ar gach {{count}} comhad" [fileSidebar.fileItem] closeViewer = "Dún an t-amharcán" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Oscail san amharcán" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Athshocraigh go réamhshocruithe" subtitle = "Cuir do chuid comhad i ngrúpaí mar mháthairchatagóirí. Cuir lipéid atá ann cheana nó lipéid nua le catagóir, athainmnigh í, nó cruthaigh do cheann féin. Taispeántar comhaid nach bhfuil in aon cheann de do chatagóirí faoi “Eile”." title = "Catagóirí an bharra taoibh" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Cuir leis an spás oibre" addToWorkspaceCount = "Cuir {{count}} leis an spás oibre" @@ -3918,7 +3971,6 @@ backToMyFiles = "Ar ais go Mo Chomhaid" breadcrumbs = "Conair an fhillteáin" cancel = "Cealaigh" classification = "Aicmiú" -clearSearch = "Glan an cuardach" clearSelection = "Glan an roghnú" closeDetails = "Dún na sonraí" create = "Cruthaigh" @@ -3950,6 +4002,7 @@ downloadAll = "Íoslódáil gach ceann" downloadVersion = "Íoslódáil an leagan seo" dropOverlay = "Scaoil comhaid chun uaslódáil" dropOverlaySub = "Tosaíonn comhaid san Áitiúil. Úsáid 'Bog go' nó 'Sábháil sa scamall' chun iad a eagrú i bhfillteán." +duplicate = "Duplicate" file = "Comhad" fileInfo = "File info" fileMenu = "Gníomhartha comhaid" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Athraigh méid chrann na bhfillteán (eochracha saigheada, S save = "Sábháil" saveToServer = "Sábháil chuig an bhfreastalaí" saveToServerDisabledHint = "Níl sábháil chuig an bhfreastalaí cumasaithe ar an bhfreastalaí seo. Iarr ar do riarthóir é a chumasú." -search = "Cuardaigh" -searchPlaceholder = "Cuardaigh an fillteán seo agus fofhillteáin" selectAll = "Roghnaigh gach rud" selectAllHint = "Cliceáil chun gach rud a roghnú. Leid: coinnigh Ctrl (nó Cmd) síos chun comhaid a chur leis ceann ar cheann, Shift chun raon a roghnú." selectedCount = "Roghnaithe: {{count}}" @@ -4044,6 +4095,10 @@ title = "Níl aon chomhaid scamaill fós" hint = "Fanann comhaid a sábháladh gan uaslódáil anseo. Scaoil comhad chun ceann a chur leis." title = "Níl aon chomhad áitiúil amháin" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Beidh comhaid a osclaíonn nó a chuireann tú in eagar le feiceáil anseo." title = "Níor athraíodh aon rud fós" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Níorbh fhéidir an fillteán a scriosadh." deleteFolderFailedDetail = "Níorbh fhéidir an fillteán a scriosadh: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Níorbh fhéidir cuma an fhillteáin a nuashonrú." folderAppearanceFailedDetail = "Níorbh fhéidir cuma an fhillteáin a nuashonrú: {{message}}" moveFilesFailed = "Níorbh fhéidir comhaid a bhogadh." @@ -4113,6 +4170,11 @@ localHint = "Stóráilte sa bhrabhsálaí seo amháin" shared = "Roinnte" sharedHint = "Roinnte leat trí nasc" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Is sine ar dtús" modifiedDesc = "Is nua ar dtús" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Níl comhaid ar fáil sa stóráil. Oscail roinnt comhad open = "Oscail" openFile = "Oscail Comhad" openFiles = "Oscail Comhaid" +selectFile = "Select {{name}}" selectFromStorage = "Roghnaigh ón Stóráil" upload = "Uaslódáil" uploadFile = "Uaslódáil Comhad" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Sábháilte go léir" +analyzingFields = "Analysing form fields..." extractCsvError = "Theip ar eastóscadh CSV" extractXlsxError = "Theip ar eastóscadh XLSX" +filled = "filled" flattenAfterFilling = "Cothromaigh tar éis líonadh" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "riach" requiredFieldsError = "Líon isteach gach réimse riachtanach le do thoil" rescanFields = "Athscan na réimsí" @@ -4795,9 +4863,6 @@ title = "Íomhá go PDF" [imageToPdf] tags = "comhshó, img, jpg, pictiúr, grianghraf" -[infoBanner] -dismiss = "Díchuir" - [invite] acceptError = "Theip ar chuntas a chruthú" accountFor = "Cuntas á chruthú do" @@ -4979,6 +5044,34 @@ uploadSuccess = "Uaslódáil Rathúil!" uploadSuccessMessage = "Tá do chuid íomhánna aistrithe." validating = "Ag bailíochtú an tseisiúin..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Scan an cód QR seo le do ghléas soghluaiste chun grianghraif a uaslódáil go díreach chuig an leathanach seo." descriptionNoConvert = "Scanáil chun grianghraif a uaslódáil ó do ghléas soghluaiste." @@ -4997,8 +5090,13 @@ title = "Uaslódáil ó Shoghluaiste" tags = "Il-Uirlis, Iloibríocht, Chomhéadain, cliceáil tarraing, ceann tosaigh, taobh an chliaint, idirghníomhach, intractable, bog" title = "Il-uirlis PDF" -[navbar] -search = "Cuardach" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Níor éirigh leis an bhfíordheimhniú. Is féidir leat an fhuinneog seo a dhúnadh agus triail eile a bhaint as." @@ -5127,6 +5225,7 @@ activeFiles = "Taispeánann an radharc Comhaid Ghníomhacha na allTools = "Seo é an painéal Gach Uirlis, áit ar féidir leat brabhsáil agus roghnú astu na huirlisí PDF uile atá ar fáil." close = "Dún" cropSettings = "Anois go bhfuil an comhad roghnaithe atá uainn a bhearradh, is féidir linn an uirlis Bearrtha a chumrú chun an limistéar a roghnú ar mian linn an PDF a bhearradh chuige." +dialogLabel = "Onboarding" fileCheckbox = "Roghnaítear comhad le próiseáil nuair a chliceálann tú air. Is féidir leat ilchomhaid a roghnú le haghaidh oibríochtaí baisc." fileReplacement = "Cuirfear an comhad athraithe in ionad an bhunchomhaid ar an mBinse Oibre go huathoibríoch, rud a fhágfaidh gur féidir leat é a rith trí níos mó uirlisí go héasca." filesButton = "Ligeann an cnaipe Comhaid ar an mBarra Rochtana Thapa duit PDFanna a uaslódáil chun na huirlisí a úsáid orthu." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF Go PDF/A" tags = "cartlann, fadtéarmach, caighdeánach, comhshó, stóráil, caomhnú" title = "PDF Go PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "priontáil,caighdeán,tiontú,táirgeadh,réamhphriontáil,cartlann" title = "PDF go PDF/X" @@ -6045,6 +6151,7 @@ small = "500 Creidiúintí" xsmall = "100 Creidiúintí" [plan.availablePlans] +currency = "Billing currency" subtitle = "Roghnaigh an plean a oireann do do riachtanais" title = "Pleananna atá ar fáil" @@ -6624,6 +6731,7 @@ reachedTitle = "Sroicheadh teorainn chaiteachais mhíosúil" title = "Níorbh fhéidir portal Stripe a oscailt" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "as {{allowance}} PDF saor in aisce úsáidte" capSuffix_other = "as {{allowance}} PDFs saor in aisce úsáidte" eyebrow = "Triail Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Seol doiciméad nó fógra ar Email nuair a ritheann polasaí." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Cuir doiciméid phróiseáilte i Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDKs oifigiúla" beta = "Beta" deprecated = "Dulta i léig" -[portal.docs.search] -empty = "Níl aon docs meaitseála" -placeholder = "Cuardaigh docs" -results = "{{count}} toradh" - [portal.docs.skills] eyebrow = "SCILEANNA" lead = "Cumasachtaí cuachta, ainmnithe a ghlaonn do ghníomhaire mar uirlis aonair. Is slabhra oibríochtaí cinntitheach gach scil, le measúnuithe ceangailte." @@ -7180,11 +7309,11 @@ editorAction = "Eagarthóir" empty = "Ní mheaitseálann aon doiciméid an scagaire seo." rowActions = "Gníomhartha ró" sensitiveLabel = "Íogair" -sensitiveTitle = "Íogair — rochtain de dhíth" [portal.documents.table.columns] action = "Píblíne / Gníomh" document = "Doiciméad" +labels = "Labels" product = "Táirge" status = "Stádas" time = "Am" @@ -7200,6 +7329,7 @@ host = "Óstach" lastSeen = "Feicthe go deireanach" region = "Réigiún" status = "Stádas" +target = "Target" version = "Leagan" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Bhuail an radharc seo earráid gan choinne. Bain triail eile as, retry = "Bain triail eile as" title = "Chuaigh rud éigin mícheart ar an leathanach seo" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Siar" body = "Íoslódáil an aip deisce nó féin-óstáil an freastalaí." @@ -7380,11 +7560,6 @@ cancel = "Cealaigh" confirm = "Cúlghair eochair" title = "Cúlghair eochair API" -[portal.infrastructure.attestationLabel] -attested = "Fianaithe" -inScope = "Faoi raon" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Scag imeachtaí iniúchta de réir catagóire" heading = "Logaí iniúchta" @@ -7456,11 +7631,6 @@ info = "Eolas" success = "D'éirigh leis" warning = "Rabhadh" -[portal.infrastructure.certLabel] -certified = "Deimhnithe" -inProgress = "Ar siúl" -notStarted = "Gan tosú" - [portal.infrastructure.createKey] cancel = "Cealaigh" createKey = "Cruthaigh eochair" @@ -7474,238 +7644,10 @@ subtitleCreated = "Cóipeáil an rún seo anois — ní thaispeánfar arís é." title = "Cruthaigh eochair API" titleCreated = "Eochair cruthaithe" -[portal.infrastructure.deployLabel] -live = "Beo" -queued = "I scuaine" -rolledBack = "Rollta siar" -rolling = "Á rolladh amach" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Imscartha ag" -environment = "Timpeallacht" -product = "Táirge" -status = "Stádas" -version = "Leagan" -when = "Cathain" - -[portal.infrastructure.deployments.recent] -heading = "Imscarthaí le déanaí" -subheading = "Na rollouts is déanaí ar fud táirgí agus timpeallachtaí." - -[portal.infrastructure.deployments.regionColumns] -instances = "Insteansanna" -latency = "Foighne" -load = "Ualach" -p99 = "P99" -region = "Réigiún" -status = "Stádas" -throughput = "Tréchur" -uptime = "Aga fónaimh" -version = "Leagan" - -[portal.infrastructure.deployments.regions] -heading = "Réigiúin" -subheading = "Sláinte bheo do gach réigiún Stirling imscartha — foighne, ualach, agus leagan rollout." - -[portal.infrastructure.deployments.regions.empty] -description = "Taispeánfar réigiúin imscartha anseo nuair a bheidh do spás oibre soláthraithe." -title = "Níl aon réigiúin imscartha" - [portal.infrastructure.keyLabel] active = "Gníomhach" revoked = "Cúlghairthe" -[portal.infrastructure.modelLabel] -active = "Gníomhach" -degraded = "Díghrádaithe" -disabled = "Díchumasaithe" - -[portal.infrastructure.models] -heading = "Samhlacha" -msValue = "{{value}} ms" -subheading = "An chatalóg samhlacha agus an ródú a chumhachtaíonn próiseáil doiciméad ar fud do spáis oibre." - -[portal.infrastructure.models.byom] -description = "Cláraigh samhail on-prem nó féin-óstáilte agus pináil í le réigiún le haghaidh próiseála atá faoi cheangal cónaitheachta sonraí." -title = "Tabhair leat do shamhail féin" - -[portal.infrastructure.models.catalogue] -heading = "Catalóg" -sub = "Samhlacha bainistithe atá ar fáil do do spás oibre, le foighne agus costas beo." -subEnterprise = "Samhlacha bainistithe, tabhair-leat-do-cheann-féin, agus on-prem — le pináil in aghaidh an réigiúin ar fáil." - -[portal.infrastructure.models.catalogue.empty] -description = "Taispeánfar samhlacha i gcatalóg do spáis oibre anseo." -title = "Níl aon samhlacha ar fáil" - -[portal.infrastructure.models.columns] -cost = "Costas" -latency = "Foighne" -load = "Ualach" -model = "Samhail" -status = "Stádas" -type = "Cineál" -version = "Leagan" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/glao" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Samhlacha gníomhacha" -avgLatency = "Meánfhoighne" -included = "San áireamh" -monthlySpend = "Caiteachas míosúil ar shamhlacha" - -[portal.infrastructure.models.routing] -empty = "Níl aon rialacha ródaithe cumraithe." -heading = "Rialacha ródaithe" -sub = "Cén tsamhail a láimhseálann gach oibríocht. Cuirtear an réamhshocrú i bhfeidhm nuair nach meaitseálann riail níos cúinge." -subLocked = "Ródáil oibríochtaí chuig samhlacha ar leith — ar fáil ar phleananna íoctha." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Uasghrádaigh go Pro chun rialú a dhéanamh ar cén tsamhail a láimhseálann gach oibríocht agus cineál doiciméid." -title = "Is gné íoctha é ródú samhlacha" - -[portal.infrastructure.models.routingColumns] -default = "Réamhshocrú" -docType = "Cineál doiciméid" -modelForAria = "Samhail do {{operation}}" -operation = "Oibríocht" -routedTo = "Ródaithe chuig" - -[portal.infrastructure.modelTypeLabel] -classification = "Aicmiú" -extraction = "Eastóscadh" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Díghrádaithe" -down = "As feidhm" -healthy = "Sláintiúil" - -[portal.infrastructure.security.access.byok] -description = "Soláthair eochair ó do KMS féin. Criptíonn Stirling léi ach is féidir leis léamh fós." -label = "Tabhair leat d’eochair féin (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Ní fhágann eochracha do KMS riamh. Ní choinníonn Stirling ach ciphertext." -label = "Coinnigh d’eochair féin (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Bainistíonn Stirling eochracha criptithe. Is simplí é — gan aon ops eochrach ar do thaobh." -label = "Eochracha á gcoinneáil ag Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Beartas rochtana doiciméad" -subheading = "Rialaíonn sé cé atá in ann doiciméid phróiseáilte atá ar fos a dhíchriptiú." - -[portal.infrastructure.security.attestations] -heading = "Fianuithe comhlíonta" -noReport = "Níl aon tuairisc ar fáil" -subheading = "Seasamh iniúchta de réir creatlaí, le tuairiscí ar fáil ar rialuithe fianaithe." -viewReport = "Féach ar thuairisc →" - -[portal.infrastructure.security.compliance] -heading = "Comhlíonadh" -subheading = "Fianuithe agus deimhnithe a chlúdaíonn ardán Stirling." - -[portal.infrastructure.security.empty] -description = "Taispeánfar cumraíocht slándála do spáis oibre anseo." -title = "Níl seasamh slándála ar fáil" - -[portal.infrastructure.security.hyokBanner] -description = "Le HYOK, ní fhágann eochracha criptithe do KMS riamh. Ní stórálann ná ní phróiseálann Stirling ach ciphertext ar féidir leat a chúlghairm am ar bith." -title = "Ní féidir le Stirling do dhoiciméid a dhíchriptiú" - -[portal.infrastructure.security.ipAllowlist] -empty = "Níl aon raonta IP cumraithe — ceadaítear gach IP." -heading = "Liosta ceadaithe IP" -sub = "Tá rochtain API srianta do na raonta CIDR seo." -subLocked = "Srian rochtain API do raonta IP aitheanta — ar fáil ar phleananna íoctha." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Uasghrádaigh go Pro chun rochtain API a shrianadh do líonraí ar leith." -title = "Is gné íoctha é liostú ceadaithe IP" - -[portal.infrastructure.security.ipColumns] -added = "Curtha leis" -addedBy = "Curtha leis ag" -cidr = "CIDR" -label = "Lipéad" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algartam" -heading = "Bainistiú eochracha criptithe" -keyId = "Aitheantóir eochrach" -lastRotated = "Rothlaithe go deireanach" -rotateKey = "Rothlaigh eochair" -rotationPolicy = "Beartas rothlaithe" -subheading = "Coimeád na n-eochracha a chriptíonn doiciméid atá ar fos — cé atá in ann díchriptiú, agus conas a rothlaítear eochracha." - -[portal.infrastructure.security.managedBanner] -description = "Tá coimeád tabhair-leat-d’eochair-féin (BYOK) agus coimeád coinnigh-d’eochair-féin (HYOK) ar fáil ar Enterprise. Uasghrádaigh chun eochracha a sholáthar ó do KMS féin." -title = "Tá eochracha á mbainistiú ag Stirling ar do phlean" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia Pacific" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · teorainn sonraí GDPR" -label = "An tAontas Eorpach" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Stáit Aontaithe Mheiriceá" - -[portal.infrastructure.security.residencyHeader] -heading = "Cónaitheacht sonraí" -subheading = "An áit a stóráiltear agus a phróiseáiltear doiciméid." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} úsáidte" - -[portal.infrastructure.storage.empty] -description = "Taispeánfar stóráil cheangailte agus úsáid anseo." -title = "Níl aon stóráil cumraithe" - -[portal.infrastructure.storage.lifecycle] -active = "Gníomhach" -activeRange = "0–{{value}}d" -archived = "Cartlannaithe" -coldStorage = "stóráil fhuar" -deleted = "Scriosta" -never = "riamh" -purged = "glanta" - -[portal.infrastructure.storage.providers] -connect = "Ceangail" -connected = "Ceangailte" -heading = "Soláthraithe ceangailte" -subheading = "An áit a scríobhtar déantáin phróiseáilte." - -[portal.infrastructure.storage.retention] -heading = "Coinneáil" -subheading = "Cá fhad a choinnítear déantáin sula scriostar iad de réir saolré." -windowLabel = "Fuinneog choinneála réamhshocraithe" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} lá" -days_other = "{{count}} lá" -never = "Ná scrios riamh" - -[portal.infrastructure.storage.totalUsage] -heading = "Úsáid iomlán" -progressLabel = "Stóráil úsáidte" -subheading = "Stóráil ídithe ar fud gach soláthraí ceangailte." - [portal.infrastructure.tabs] apiKeys = "Eochracha API" audit = "Logaí Iniúchta" @@ -7715,11 +7657,9 @@ security = "Slándáil" storage = "Stóráil" [portal.integrations] -addAnother = "Cuir ceann eile leis" availableHeading = "Ar fáil" comingSoonHeading = "Ag teacht go luath" connect = "Ceangail" -connectedHeading = "Ceangailte" connectionCount_one = "{{count}} nasc" connectionCount_other = "{{count}} nasc" customApi = "API saincheaptha" @@ -7737,6 +7677,10 @@ security = "Slándáil" signing = "Síniú" storage = "Stóráil" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Ceangailte" @@ -7767,7 +7711,6 @@ integrations = "Comhtháthuithe" pipelines = "Píblínte" policies = "Beartais" procurement = "Soláthar" -settings = "Socruithe" sources = "Foinsí" usage = "Úsáid & Billeáil" users = "Úsáideoirí" @@ -7784,7 +7727,7 @@ title = "Píblínte" newPipeline = "Píblíne nua" [portal.pipelines.builder] -addStep = "Cuir uirlis leis" +activate = "Activate" back = "Ar ais chuig píblínte" cannotFollow = "Ní féidir {{produced}} a ghlacadh" chooseAccount = "Roghnaigh cuntas" @@ -7792,28 +7735,38 @@ chooseDestination = "Roghnaigh ceann scríbe" chooseOperation = "Roghnaigh cad a dhéanann an chéim seo" chooseSource = "Roghnaigh foinse" discard = "Caith athruithe amach" -enabled = "Cumasaithe" inputs = "Ionchur" inputSource = "Foinse ionchuir" inputTrigger = "Truicear" keepEditing = "Lean den eagarthóireacht" +moreActions = "More actions" needsConfiguring = "Teastaíonn socrú" -needsUpload = "Teastaíonn comhad uaslódáilte" -noSources = "Níl aon fhoinsí ceangailte fós. Cruthaigh ceann chun é a úsáid mar ionchur." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Ní mheaitseálann aon uirlisí do chuardach." -pipelineSettings = "Socruithe píblíne" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Cuardaigh uirlisí" -selectToolBody = "Cuir uirlis leis chun do phíblíne a thógáil." -selectToolTitle = "Níl aon uirlisí fós" sendToSystem = "Seol chuig córas eile" stepsIncompatible = "Ní féidir leis na céimeanna seo rith ar an méid a tháirgeann an chéim roimhe seo: {{tools}}." stepsNeedSetup = "Tá socrú fós ag teastáil ó na céimeanna seo sula sábhálfar: {{tools}}." -toolSettings = "Socruithe uirlise" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Oibríocht neamhaitheanta, coinnithe mar atá." unsavedBody = "Tá athruithe gan sábháil agat. Sábháil iad sula bhfágann tú, nó caith amach iad?" unsavedTitle = "Athruithe gan sábháil" -uploadUnsupported = "Ní thacaítear le comhaid uaslódáilte i bpíblínte fós, mar sin ní féidir na céimeanna seo a shábháil: {{tools}}." usesDefaults = "Ritheann le socruithe réamhshocraithe" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Comhcheanglaíonn sé gach comhad ón gcéim roimhe seo" @@ -7825,31 +7778,36 @@ undeclared-operation = "Ní féidir a sheiceáil cad a ghlacann an chéim seo" [portal.pipelines.composer] addTool = "Cuir uirlis leis" -cancel = "Cealaigh" -chainEmpty = "Cuir uirlis leis chun tosú ag tógáil do phíblíne." create = "Cruthaigh píblíne" +createPaused = "Create paused" editingUnsupported = "Ní thacaítear fós le taispeáint na bparaiméadar uirlise seo le haghaidh eagarthóireachta." -moveDown = "Bog síos" -moveUp = "Bog suas" +editSource = "Edit source" name = "Ainm" namePlaceholder = "m.sh. Scuabadh eagarthóireachta" noToolSettings = "Níl aon socruithe inchumraithe ag an uirlis seo." -operations_one = "Oibríocht ({{count}})" -operations_other = "Oibríochtaí ({{count}})" output = "Aschur" -removeStep = "Bain oibríocht" save = "Sábháil athruithe" scheduleEvery = "Rith gach" -sources = "Foinsí" -sourcesLoading = "Foinsí á luchtú..." trigger = "Truicear" triggerManual = "Láimhe amháin" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "lá" hours = "uair" minutes = "nóiméad" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Scrios \"{{name}}\"? Ní féidir é seo a chealú." cancel = "Cealaigh" @@ -7867,6 +7825,37 @@ connectSource = "Ceangail foinse" description = "Cruthaigh do chéad phíblíne: roghnaigh na foinsí a ritheann sí orthu, slabhraigh na hoibríochtaí, agus roghnaigh cá dtéann an t-aschur." title = "Níl aon phíblínte fós" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Gníomhach" paused = "Ar sos" @@ -7879,7 +7868,6 @@ completed_one = "Rith críochnaithe." completed_other = "Críochnaíodh gach {{count}} rith." empty = "Níl aon rud le rith: ní raibh aon doiciméid le próiseáil ag na foinsí." failed = "Theip ar an rith: {{error}}" -historyCleared = "Glanadh an stair. Próiseálfaidh an chéad rith eile gach rud atá sna foinsí faoi láthair arís." inFlight = "Níl aon rud nua le rith: tá doiciméid fós á bpróiseáil ó rith níos luaithe." parked_one = "Níl aon rud le rith: theip ar {{count}} doiciméad roimhe seo agus tá sé páirceáilte. Ceartaigh an chúis, ansin glan an stair chun triail eile a bhaint as." parked_other = "Níl aon rud le rith: theip ar {{count}} doiciméad roimhe seo agus tá siad páirceáilte. Ceartaigh an chúis, ansin glan an stair chun triail eile a bhaint astu." @@ -7895,6 +7883,7 @@ name = "Píblíne" sources = "Foinsí" status = "Stádas" steps = "Céimeanna" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Faire fillteáin" @@ -8209,6 +8198,24 @@ label = "Ceangail le saincheist Jira" description = "Seolann sé an doiciméad próiseáilte mar cheangaltán." label = "Seol an doiciméad ar ríomhphost (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Scríobhann sé an doiciméad próiseáilte isteach i bhfillteán." label = "Uaslódáil chuig Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Uasmhéadaithe" subtitle = "Maireann do thriail saor in aisce go dtí {{date}}. Níl cárta de dhíth." title = "Triail Enterprise" -[portal.search] -ariaLabel = "Cuardaigh" -placeholder = "Cuardaigh Stirling — endpoints, píblínte, cáipéisí…" - -[portal.search.empty] -noActionsDescription = "Beidh gníomhartha tapa le feiceáil anseo nuair a bheidh siad ar fáil." -noActionsTitle = "Níl aon ghníomhartha tapa" -noMatches = "Níl aon mheaitseálacha do \"{{query}}\"" -noMatchesDescription = "Bain triail as eochairfhocal eile nó brabhsáil an chatalóg." - [portal.settings.groups] admin = "Riarachán" @@ -8743,10 +8740,6 @@ cancel = "Cealaigh" confirm = "Scrios" title = "Scrios foinse?" -[portal.sources.empty] -description = "Ceangail fillteán (agus, go luath, stóráil scamall) ionas go mbeidh áit ag do bheartais le cáipéisí a tharraingt uaithi." -title = "Níl aon fhoinsí ceangailte fós" - [portal.sources.kpi] inUse = "In úsáid" total = "Naisc" @@ -8785,6 +8778,7 @@ unused = "Gan úsáid" documents = "Doiciméid" source = "Foinse" status = "Stádas" +type = "Type" usedBy = "Beartais" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Polasaithe agus Príobháideachas" [settings.preferences] title = "Sainroghanna" -[settings.search] -placeholder = "Déan cuardach ar leathanaigh socruithe..." - [settings.security] description = "Nuashonraigh do phasfhocal chun do chuntas a choinneáil slán." title = "Slándáil" @@ -10128,6 +10119,7 @@ last = "An leathanach deiridh" maintainRatio = "Scoránaigh, coinnigh an cóimheas gné" next = "An chéad leathanach eile" noSavedSigs = "Níor aimsíodh aon síniú sábháilte" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Sínithe Pearsanta" previous = "Leathanach roimhe seo" redo = "Athdhéan" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Theip ar bhaint an chúlra" hint = "Uaslódáil íomhá PNG nó JPG de do shíniú" label = "Uaslódáil íomhá sínithe" placeholder = "Roghnaigh comhad íomhá" +previewAlt = "Current image signature" processing = "Íomhá á phróiseáil..." removeBackground = "Bain an cúlra bán (déan trédhearcach)" @@ -10179,6 +10172,17 @@ saved = "Roghnaigh síniú sábháilte thuas, ansin cliceáil áit ar bith ar an text = "Tar éis duit d’ainm a chur isteach thuas, cliceáil áit ar bith ar an PDF chun do shíniú a chur." title = "Conas síniú a chur leis" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Bog an Síniú" pause = "Cuir suíniú ar sos" @@ -10625,6 +10629,23 @@ title = "Uaslódáil chuig an bhfreastalaí" updateButton = "Nuashonraigh ar an bhfreastalaí" uploadButton = "Uaslódáil chuig an bhfreastalaí" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Téigh ar ais" pdfTools = "Uirlisí PDF" placeholder = "Roghnaigh uirlis chun tosú" premiumFeature = "Gné Préimhe:" -searchTools = "Cuardaigh uirlisí" toolsHeader = "Uirlisí" viewAllTools = "Féach ar gach uirlis" @@ -10822,6 +10842,9 @@ removal = "Baint" signing = "Síniú" verification = "Fíorú" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Níor aimsíodh uirlisí" noTools = "Níl aon uirlisí ar fáil" @@ -10979,6 +11002,13 @@ approver = "Faomhann beartas" editor = "Eagarthóir" processor = "Próiseálaí" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Cealaigh an cuireadh chuig {{email}}? Ní bheidh siad in ann dul isteach leis an nasc reatha." cancelInviteTitle = "Cealaigh cuireadh" @@ -10998,10 +11028,8 @@ title = "Níl aon bhaill fós" addToTeam = "Cuir le foireann" guestCount = "{{count}} aoi" guests = "Aíonna" -guestsDesc = "Comhoibrithe seachtracha, teoranta don rud a chomhroinn tú. Eagarthóir amháin." ledBy = "faoi stiúir {{owner}}" org = "Eagraíocht" -orgDesc = "Úinéirí le húdarás ar fud na heagraíochta agus faomhadh beartas" owners = "{{count}} úinéir" team = "foireann {{name}}" teamMeta = "{{count}} duine" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Cuireadh ó {{who}}" cancel = "Cealaigh" -count = "{{count}} ar feitheamh" -desc = "Daoine ar tugadh cuireadh dóibh nár tháinig isteach fós. Coinníonn siad suíochán go dtí go nglacann siad leis." expiresInDays_one = "Rachaidh sé in éag i gceann {{count}} lá" expiresInDays_other = "Rachaidh sé in éag i gceann {{count}} lá" expiresToday = "Rachaidh sé in éag inniu" title = "Cuirí ar feitheamh" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Chuaigh rud éigin mícheart agus an backend á bhaint amach, nó níl rochtain agat. Bain triail eile as." title = "Níorbh fhéidir baill a lódáil" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Cuardaigh iatáin" title = "Iatáin" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Teideal an leabharmhairc" bookmarkTitleRequired = "Tá teideal an leabharmhairc riachtanach" closeSidebar = "Dún barra taoibh na leabharmharcanna" collapseAll = "Laghdaigh gach leabharmharc" +empty = "No bookmarks in this document" expandAll = "Leathnaigh gach leabharmharc" searchPlaceholder = "Cuardaigh leabharmharcanna" +title = "Bookmarks" [viewer.comments] addComment = "Cuir trácht leis" @@ -11264,10 +11297,12 @@ locateAnnotation = "Aimsigh sa doiciméad" moreActions = "Tuilleadh gníomhartha" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Leathanach {{page}}" placingHint = "Cliceáil ar leathanach chun é a chur… (cealaigh)" removeCommentOnly = "Bain an trácht amháin" saveReply = "Sábháil freagra" +searchPlaceholder = "Search comments" title = "Tráchtanna" typeComment = "Trácht" typeInsertText = "Ionsáigh Téacs" @@ -11290,6 +11325,7 @@ unsavedDesc = "Tá athruithe gan sábháil agat" closeSidebar = "Dún barra taoibh na sraitheanna" hideAll = "Folaigh gach sraith" showAll = "Taispeáin gach sraith" +title = "Layers" [viewer.link] delete = "Scrios nasc" @@ -11646,6 +11682,7 @@ exitRedaction = "Scoir de mhód ceilte" exportAll = "Easpórtáil PDF" exportSelected = "Easpórtáil na leathanaigh roghnaithe" formFill = "Líon foirm" +hideToolbar = "Hide toolbar" multiTool = "Il-uirlis" panMode = "Mód pánála" print = "Priontáil PDF" @@ -11666,6 +11703,7 @@ selectAll = "Roghnaigh uile" selectByNumber = "Roghnaigh de réir uimhreacha leathanaigh" selectLanguage = "Roghnaigh teanga" share = "Roinn" +showToolbar = "Show toolbar" toggleAnnotations = "Scoránaigh infheictheacht na n-anótálacha" toggleAttachments = "Scoránaigh iatáin" toggleBookmarks = "Scoránaigh leabharmharcanna" diff --git a/frontend/editor/public/locales/hi-IN/translation.toml b/frontend/editor/public/locales/hi-IN/translation.toml index 01a20654dc..0e122c9980 100644 --- a/frontend/editor/public/locales/hi-IN/translation.toml +++ b/frontend/editor/public/locales/hi-IN/translation.toml @@ -1,7 +1,6 @@ alphabet = "वर्णमाला" apply = "लागू करें" applyAndContinue = "सहेजें और बाहर निकलें" -areYouSure = "क्या आप वाकई छोड़ना चाहते हैं?" back = "वापस" black = "काला" blue = "नीला" @@ -106,6 +105,7 @@ undoSuccess = "ऑपरेशन सफलतापूर्वक पूर् unknown = "अज्ञात" unpin = "फ़ाइल अनपिन करें (टूल चलने के बाद प्रतिस्थापित करें)" unsavedChanges = "आपके PDF में असहेजे परिवर्तन हैं।" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "असहेजे परिवर्तन" unsupported = "असमर्थित" username = "उपयोगकर्ता नाम" @@ -1879,6 +1879,9 @@ width = "चौड़ाई" [app] description = "मुफ़्त Adobe Acrobat विकल्प (10M+ डाउनलोड)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "PDF/A-3b में बदलें" convertToPdfA3bDescription = "एम्बेडेड अटैचमेंट्स के साथ एक आर्काइवल PDF बनाता है" @@ -2933,12 +2936,16 @@ text = "इन अनुमतियों को अपरिवर्तनी [chat.actions] copy = "संदेश कॉपी करें" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling एजेंट विकल्प" +assistant = "AI Assistant" clearChat = "चैट साफ़ करें" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "यह ऑपरेशन आपके क्लाउड क्र tooltip = "स्क्रीन से रंग चुनें" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "रंग चुनें" [common] +actions = "Actions" back = "वापस" cancel = "रद्द करें" close = "बंद करें" +codeSample = "Code sample" collapse = "समेटें" confirm = "पुष्टि करें" continue = "जारी रखें" copied = "कॉपी हो गया!" copy = "कॉपी करें" done = "पूर्ण" +enterValue = "Enter value" error = "त्रुटि" expand = "विस्तारित करें" loading = "लोड हो रहा है..." next = "अगला" +open = "Open" preview = "पूर्वावलोकन" previous = "पिछला" refresh = "रीफ़्रेश करें" remaining = "शेष" retry = "फिर से प्रयास करें" save = "सहेजें" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "चयनित साफ़ करें" @@ -3185,6 +3199,7 @@ title = "संपीड़न विधि" [compress.settings] desiredSize = "वांछित फ़ाइल आकार" desiredSizePlaceholder = "आकार दर्ज करें" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "संपीड़न फ़ाइल आकार कम करने का आसान तरीका है। 'File Size' चुनें और लक्ष्य आकार दर्ज करें ताकि हम आपके लिए गुणवत्ता समायोजित कर दें। 'Quality' चुनें ताकि आप संपीड़न की तीव्रता मैन्युअल रूप से सेट कर सकें।" @@ -3387,6 +3402,24 @@ pdfOptions = "PDF विकल्प" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "पहले एक स्रोत फ़ॉर्मेट चुनें" settings = "सेटिंग्स" single = "एकल" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "खोजें..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "संपादित करें" @@ -3824,11 +3861,13 @@ shareSelected = "चयनित साझा करें" sharing = "शेयरिंग" showAll = "सभी दिखाएँ" showHistory = "इतिहास दिखाएँ" +sortBy = "Sort files" sortByDate = "तिथि के अनुसार क्रमबद्ध करें" sortByName = "नाम के अनुसार क्रमबद्ध करें" sortBySize = "आकार के अनुसार क्रमबद्ध करें" storage = "स्टोरेज" storageState = "भंडारण" +storageUsed = "Storage used" synced = "सिंक किया गया" title = "PDF फ़ाइलें अपलोड करें" toolChain = "लागू किए गए टूल" @@ -3874,8 +3913,12 @@ addFiles = "फ़ाइलें जोड़ें" addingFiles = "फ़ाइलें जोड़ी जा रही हैं…" collapse = "साइडबार समेटें" customizeGroups = "समूह अनुकूलित करें" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "शुरू करने के लिए फ़ाइलें खोलें" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "साइडबार फैलाएँ" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive कॉन्फ़िगर नहीं है" @@ -3888,15 +3931,17 @@ openFromComputer = "कंप्यूटर से खोलें" openSettings = "सेटिंग्स खोलें" other = "अन्य" recent = "हाल के" -search = "खोजें" -searchPlaceholder = "फ़ाइलें खोजें..." viewAll = "सभी {{count}} फ़ाइलें देखें" [fileSidebar.fileItem] closeViewer = "व्यूअर बंद करें" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "व्यूअर में खोलें" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "डिफ़ॉल्ट पर रीसेट करें" subtitle = "अपनी फ़ाइलों को मूल श्रेणियों में समूहित करें। किसी श्रेणी में मौजूदा या नए लेबल जोड़ें, उसका नाम बदलें, या अपनी खुद की श्रेणी बनाएँ। आपकी किसी भी श्रेणी में न आने वाली फ़ाइलें “अन्य” के अंतर्गत दिखाई देती हैं।" title = "Sidebar श्रेणियाँ" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "वर्कस्पेस में जोड़ें" addToWorkspaceCount = "{{count}} को वर्कस्पेस में जोड़ें" @@ -3918,7 +3971,6 @@ backToMyFiles = "मेरी फ़ाइलों पर वापस" breadcrumbs = "फ़ोल्डर पथ" cancel = "रद्द करें" classification = "वर्गीकरण" -clearSearch = "खोज साफ़ करें" clearSelection = "चयन साफ़ करें" closeDetails = "विवरण बंद करें" create = "बनाएं" @@ -3950,6 +4002,7 @@ downloadAll = "सभी डाउनलोड करें" downloadVersion = "यह संस्करण डाउनलोड करें" dropOverlay = "अपलोड करने के लिए फ़ाइलें छोड़ें" dropOverlaySub = "फ़ाइलें Local में शुरू होती हैं। उन्हें किसी फ़ोल्डर में व्यवस्थित करने के लिए 'Move to' या 'Save to cloud' का उपयोग करें।" +duplicate = "Duplicate" file = "फ़ाइल" fileInfo = "File info" fileMenu = "फ़ाइल क्रियाएँ" @@ -3983,8 +4036,6 @@ resizeFolderTree = "फ़ोल्डर ट्री का आकार ब save = "सहेजें" saveToServer = "सर्वर पर सहेजें" saveToServerDisabledHint = "इस सर्वर पर सहेजना सक्षम नहीं है। अपने प्रशासक से इसे सक्षम करने के लिए कहें।" -search = "खोज" -searchPlaceholder = "इस फ़ोल्डर और उप-फ़ोल्डरों में खोजें" selectAll = "सभी चुनें" selectAllHint = "सभी चुनने के लिए क्लिक करें। सुझाव: एक-एक करके फ़ाइलें जोड़ने के लिए Ctrl (या Cmd) दबाए रखें, रेंज चुनने के लिए Shift।" selectedCount = "{{count}} चयनित" @@ -4044,6 +4095,10 @@ title = "अभी तक कोई क्लाउड फ़ाइल नही hint = "बिना अपलोड सहेजी गई फ़ाइलें यहाँ रहती हैं। जोड़ने के लिए कोई फ़ाइल छोड़ें।" title = "कोई केवल-लोकल फ़ाइल नहीं" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "जो फ़ाइलें आप खोलते या संपादित करते हैं, वे यहाँ दिखाई देंगी।" title = "अभी तक कुछ संशोधित नहीं" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "फ़ोल्डर हटाया नहीं जा सका।" deleteFolderFailedDetail = "फ़ोल्डर हटाया नहीं जा सका: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "फ़ोल्डर दिखावट अपडेट नहीं हो सकी।" folderAppearanceFailedDetail = "फ़ोल्डर दिखावट अपडेट नहीं हो सकी: {{message}}" moveFilesFailed = "फ़ाइलें ले जाई नहीं जा सकीं।" @@ -4113,6 +4170,11 @@ localHint = "केवल इस ब्राउज़र में संग् shared = "शेयर किया गया" sharedHint = "लिंक के माध्यम से आपके साथ साझा" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "सबसे पुराने पहले" modifiedDesc = "हाल के पहले" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "स्टोरेज में कोई फ़ाइ open = "खोलें" openFile = "फ़ाइल खोलें" openFiles = "फ़ाइलें खोलें" +selectFile = "Select {{name}}" selectFromStorage = "स्टोरेज से चुनें" upload = "अपलोड" uploadFile = "फ़ाइल अपलोड करें" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "सब सहेजा गया" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV extract करने में विफल" extractXlsxError = "XLSX extract करने में विफल" +filled = "filled" flattenAfterFilling = "भरने के बाद समतल करें" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "आव." requiredFieldsError = "कृपया सभी आवश्यक fields भरें" rescanFields = "फ़ील्ड्स दोबारा स्कैन करें" @@ -4795,9 +4863,6 @@ title = "छवि से PDF" [imageToPdf] tags = "रूपांतरण,img,jpg,चित्र,फोटो" -[infoBanner] -dismiss = "खारिज करें" - [invite] acceptError = "खाता बनाने में विफल" accountFor = "के लिए खाता बना रहे हैं" @@ -4979,6 +5044,34 @@ uploadSuccess = "अपलोड सफल!" uploadSuccessMessage = "आपकी छवियाँ स्थानांतरित कर दी गई हैं।" validating = "सेशन सत्यापित किया जा रहा है..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "फ़ोटो अपलोड करने के लिए स्कैन करें। छवियाँ स्वतः PDF में बदल जाती हैं।" descriptionNoConvert = "अपने मोबाइल डिवाइस से फ़ोटो अपलोड करने के लिए स्कैन करें।" @@ -4997,8 +5090,13 @@ title = "मोबाइल से अपलोड करें" tags = "मल्टी टूल,मल्टी ऑपरेशन,UI,क्लिक ड्रैग,फ्रंट एंड,क्लाइंट साइड,इंटरैक्टिव,इंटरैक्टेबल,मूव,डिलीट,माइग्रेट,डिवाइड" title = "PDF मल्टी टूल" -[navbar] -search = "खोजें" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "प्रमाणीकरण सफल नहीं हुआ। आप यह विंडो बंद कर फिर से प्रयास कर सकते हैं।" @@ -5127,6 +5225,7 @@ activeFiles = "Active Files दृश्य उन सभी PD allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "बंद करें" cropSettings = "अब जब हमने वह फ़ाइल चुन ली है जिसे क्रॉप करना है, तो हम Crop टूल को कॉन्फ़िगर कर सकते हैं ताकि वह क्षेत्र चुनें जहाँ तक PDF को क्रॉप करना है।" +dialogLabel = "Onboarding" fileCheckbox = "किसी फ़ाइल पर क्लिक करने से वह प्रोसेसिंग के लिए चुनी जाती है। बैच ऑपरेशंस के लिए आप कई फ़ाइलें चुन सकते हैं।" fileReplacement = "संशोधित फ़ाइल Workbench में मूल फ़ाइल को स्वचालित रूप से बदल देगी, जिससे आपको इसे और टूल्स से चलाना आसान हो जाएगा।" filesButton = "क्विक एक्सेस बार पर Files बटन आपको PDF अपलोड करने देता है जिन पर आप टूल्स का उपयोग कर सकते हैं।" @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF से PDF/A" tags = "संग्रह,लंबी अवधि,मानक,रूपांतरण,भंडारण,संरक्षण" title = "PDF से PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "print,standard,conversion,production,prepress,archive" title = "PDF से PDF/X" @@ -6045,6 +6151,7 @@ small = "500 क्रेडिट्स" xsmall = "100 क्रेडिट्स" [plan.availablePlans] +currency = "Billing currency" subtitle = "अपनी जरूरतों के अनुसार प्लान चुनें" title = "उपलब्ध प्लान" @@ -6624,6 +6731,7 @@ reachedTitle = "मासिक spend limit पहुँच गई" title = "Stripe portal नहीं खोला जा सका" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "{{allowance}} मुफ़्त PDFs में से उपयोग हुआ" capSuffix_other = "{{allowance}} मुफ़्त PDFs में से उपयोग हुआ" eyebrow = "Processor trial" @@ -6873,6 +6981,32 @@ label = "Jira" description = "policy run होने पर document या notification email करें।" label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "processed documents को Nextcloud में file करें।" @@ -7077,11 +7211,6 @@ title = "Official SDKs" beta = "Beta" deprecated = "Deprecated" -[portal.docs.search] -empty = "कोई matching docs नहीं" -placeholder = "docs search करें" -results = "{{count}} results" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Bundled, named capabilities जिन्हें आपका agent single tool के रूप में invoke करता है। हर skill deterministic op chain है जिसके साथ evals attached हैं।" @@ -7180,11 +7309,11 @@ editorAction = "संपादक" empty = "इस फ़िल्टर से कोई दस्तावेज़ मेल नहीं खाते।" rowActions = "पंक्ति कार्रवाइयाँ" sensitiveLabel = "संवेदनशील" -sensitiveTitle = "संवेदनशील — एक्सेस आवश्यक" [portal.documents.table.columns] action = "पाइपलाइन / कार्रवाई" document = "दस्तावेज़" +labels = "Labels" product = "उत्पाद" status = "स्थिति" time = "समय" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "अंतिम बार देखा गया" region = "क्षेत्र" status = "स्थिति" +target = "Target" version = "Version" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "इस व्यू में अनपेक्षित त् retry = "फिर से कोशिश करें" title = "इस पेज पर कुछ गलत हो गया" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "वापस" body = "desktop app download करें या server self-host करें।" @@ -7380,11 +7560,6 @@ cancel = "रद्द करें" confirm = "key रद्द करें" title = "API key रद्द करें" -[portal.infrastructure.attestationLabel] -attested = "प्रमाणित" -inScope = "दायरे में" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "श्रेणी के आधार पर audit events फ़िल्टर करें" heading = "Audit logs" @@ -7456,11 +7631,6 @@ info = "जानकारी" success = "सफलता" warning = "चेतावनी" -[portal.infrastructure.certLabel] -certified = "प्रमाणित" -inProgress = "प्रगति में" -notStarted = "शुरू नहीं हुआ" - [portal.infrastructure.createKey] cancel = "रद्द करें" createKey = "key बनाएँ" @@ -7474,238 +7644,10 @@ subtitleCreated = "इस secret को अभी कॉपी करें — title = "API key बनाएँ" titleCreated = "Key बनाई गई" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "कतार में" -rolledBack = "रोल बैक किया गया" -rolling = "रोल आउट हो रहा है" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "डिप्लॉय किया" -environment = "Environment" -product = "उत्पाद" -status = "स्थिति" -version = "Version" -when = "कब" - -[portal.infrastructure.deployments.recent] -heading = "हाल के डिप्लॉयमेंट" -subheading = "Products और environments में नवीनतम rollouts." - -[portal.infrastructure.deployments.regionColumns] -instances = "इंस्टेंस" -latency = "Latency" -load = "Load" -p99 = "P99" -region = "क्षेत्र" -status = "स्थिति" -throughput = "Throughput" -uptime = "Uptime" -version = "Version" - -[portal.infrastructure.deployments.regions] -heading = "क्षेत्र" -subheading = "हर डिप्लॉय किए गए Stirling क्षेत्र का live health — latency, load, और rollout version." - -[portal.infrastructure.deployments.regions.empty] -description = "आपका workspace provision होने के बाद डिप्लॉय किए गए क्षेत्र यहाँ दिखाई देंगे।" -title = "कोई क्षेत्र डिप्लॉय नहीं" - [portal.infrastructure.keyLabel] active = "सक्रिय" revoked = "रद्द" -[portal.infrastructure.modelLabel] -active = "सक्रिय" -degraded = "कमज़ोर" -disabled = "अक्षम" - -[portal.infrastructure.models] -heading = "Models" -msValue = "{{value}} ms" -subheading = "आपके workspace में document processing को power देने वाला model catalogue और routing." - -[portal.infrastructure.models.byom] -description = "On-prem या self-hosted model रजिस्टर करें और data-residency-bound processing के लिए उसे किसी क्षेत्र में pin करें।" -title = "अपना model लाएँ" - -[portal.infrastructure.models.catalogue] -heading = "Catalogue" -sub = "आपके workspace के लिए उपलब्ध managed models, live latency और cost के साथ।" -subEnterprise = "Managed, bring-your-own, और on-prem models — per-region pinning उपलब्ध।" - -[portal.infrastructure.models.catalogue.empty] -description = "आपके workspace के catalogue में models यहाँ दिखाई देंगे।" -title = "कोई models उपलब्ध नहीं" - -[portal.infrastructure.models.columns] -cost = "लागत" -latency = "Latency" -load = "Load" -model = "Model" -status = "स्थिति" -type = "प्रकार" -version = "Version" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/call" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "सक्रिय models" -avgLatency = "औसत latency" -included = "शामिल" -monthlySpend = "मासिक model खर्च" - -[portal.infrastructure.models.routing] -empty = "कोई routing rules कॉन्फ़िगर नहीं।" -heading = "Routing rules" -sub = "प्रत्येक operation को कौन सा model संभालता है। जब कोई संकीर्ण rule मेल नहीं खाता, तो default लागू होता है।" -subLocked = "Operations को विशिष्ट models तक route करें — paid plans पर उपलब्ध।" - -[portal.infrastructure.models.routing.lockedBanner] -description = "प्रत्येक operation और document type को कौन सा model संभालता है, इसे नियंत्रित करने के लिए Pro में अपग्रेड करें।" -title = "Model routing एक paid feature है" - -[portal.infrastructure.models.routingColumns] -default = "Default" -docType = "दस्तावेज़ प्रकार" -modelForAria = "{{operation}} के लिए model" -operation = "Operation" -routedTo = "Route किया गया" - -[portal.infrastructure.modelTypeLabel] -classification = "वर्गीकरण" -extraction = "निष्कर्षण" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "कमज़ोर" -down = "Down" -healthy = "स्वस्थ" - -[portal.infrastructure.security.access.byok] -description = "अपने KMS से key दें। Stirling इसके साथ encrypt करता है लेकिन अभी भी पढ़ सकता है।" -label = "अपनी key लाएँ (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Keys कभी आपके KMS से बाहर नहीं जातीं। Stirling केवल ciphertext रखता है।" -label = "अपनी key अपने पास रखें (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling encryption keys प्रबंधित करता है। सबसे सरल — आपकी ओर से zero key ops." -label = "Stirling-held keys" - -[portal.infrastructure.security.accessPolicy] -heading = "दस्तावेज़ एक्सेस पॉलिसी" -subheading = "यह नियंत्रित करता है कि rest पर processed documents को कौन decrypt कर सकता है।" - -[portal.infrastructure.security.attestations] -heading = "Compliance attestations" -noReport = "कोई report उपलब्ध नहीं" -subheading = "Framework-by-framework audit posture, attested controls पर reports उपलब्ध।" -viewReport = "Report देखें →" - -[portal.infrastructure.security.compliance] -heading = "Compliance" -subheading = "Stirling platform को कवर करने वाले attestations और certifications." - -[portal.infrastructure.security.empty] -description = "आपके workspace की security configuration यहाँ दिखाई देगी।" -title = "Security posture उपलब्ध नहीं" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK के साथ, encryption keys कभी आपके KMS से बाहर नहीं जातीं। Stirling केवल ciphertext स्टोर और process करता है, जिसे आप किसी भी समय revoke कर सकते हैं।" -title = "Stirling आपके दस्तावेज़ decrypt नहीं कर सकता" - -[portal.infrastructure.security.ipAllowlist] -empty = "कोई IP ranges कॉन्फ़िगर नहीं — सभी IPs अनुमत।" -heading = "IP allowlist" -sub = "API access इन CIDR ranges तक सीमित है।" -subLocked = "API access को ज्ञात IP ranges तक सीमित करें — paid plans पर उपलब्ध।" - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "API access को विशिष्ट networks तक सीमित करने के लिए Pro में अपग्रेड करें।" -title = "IP allowlisting एक paid feature है" - -[portal.infrastructure.security.ipColumns] -added = "जोड़ा गया" -addedBy = "जोड़ा" -cidr = "CIDR" -label = "Label" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "Encryption key management" -keyId = "Key identifier" -lastRotated = "अंतिम बार रोटेट किया गया" -rotateKey = "key रोटेट करें" -rotationPolicy = "Rotation policy" -subheading = "वे keys जो rest पर documents को encrypt करती हैं — कौन decrypt कर सकता है, और keys कैसे rotate होती हैं।" - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key (BYOK) और hold-your-own-key (HYOK) custody Enterprise पर उपलब्ध हैं। अपने KMS से keys देने के लिए अपग्रेड करें।" -title = "आपके plan पर keys Stirling द्वारा managed हैं" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "एशिया प्रशांत" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR data boundary" -label = "यूरोपीय संघ" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "संयुक्त राज्य" - -[portal.infrastructure.security.residencyHeader] -heading = "Data residency" -subheading = "जहाँ दस्तावेज़ स्टोर और process किए जाते हैं।" - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} उपयोग किया गया" - -[portal.infrastructure.storage.empty] -description = "Connected storage और उपयोग यहाँ दिखाई देंगे।" -title = "कोई storage कॉन्फ़िगर नहीं" - -[portal.infrastructure.storage.lifecycle] -active = "सक्रिय" -activeRange = "0–{{value}}d" -archived = "Archived" -coldStorage = "cold storage" -deleted = "हटाया गया" -never = "कभी नहीं" -purged = "purged" - -[portal.infrastructure.storage.providers] -connect = "कनेक्ट करें" -connected = "कनेक्टेड" -heading = "Connected providers" -subheading = "जहाँ processed artifacts लिखे जाते हैं।" - -[portal.infrastructure.storage.retention] -heading = "Retention" -subheading = "Lifecycle deletion से पहले artifacts कितने समय तक रखे जाते हैं।" -windowLabel = "Default retention window" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} दिन" -days_other = "{{count}} दिन" -never = "कभी न हटाएँ" - -[portal.infrastructure.storage.totalUsage] -heading = "कुल उपयोग" -progressLabel = "उपयोग किया गया storage" -subheading = "सभी connected providers में खपत हुआ storage." - [portal.infrastructure.tabs] apiKeys = "API Keys" audit = "Audit Logs" @@ -7715,11 +7657,9 @@ security = "Security" storage = "Storage" [portal.integrations] -addAnother = "एक और जोड़ें" availableHeading = "उपलब्ध" comingSoonHeading = "जल्द आ रहा है" connect = "कनेक्ट करें" -connectedHeading = "कनेक्टेड" connectionCount_one = "{{count}} कनेक्शन" connectionCount_other = "{{count}} कनेक्शन" customApi = "कस्टम API" @@ -7737,6 +7677,10 @@ security = "सुरक्षा" signing = "हस्ताक्षर" storage = "स्टोरेज" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "कनेक्टेड" @@ -7767,7 +7711,6 @@ integrations = "इंटीग्रेशन" pipelines = "पाइपलाइन्स" policies = "पॉलिसियाँ" procurement = "Procurement" -settings = "सेटिंग्स" sources = "स्रोत" usage = "Usage & Billing" users = "उपयोगकर्ता" @@ -7784,7 +7727,7 @@ title = "पाइपलाइन्स" newPipeline = "नई पाइपलाइन" [portal.pipelines.builder] -addStep = "टूल जोड़ें" +activate = "Activate" back = "पाइपलाइन्स पर वापस जाएँ" cannotFollow = "{{produced}} नहीं ले सकता" chooseAccount = "एक खाता चुनें" @@ -7792,28 +7735,38 @@ chooseDestination = "एक गंतव्य चुनें" chooseOperation = "चुनें कि यह चरण क्या करता है" chooseSource = "एक स्रोत चुनें" discard = "बदलाव छोड़ें" -enabled = "सक्षम" inputs = "इनपुट" inputSource = "इनपुट स्रोत" inputTrigger = "ट्रिगर" keepEditing = "संपादन जारी रखें" +moreActions = "More actions" needsConfiguring = "सेटअप आवश्यक है" -needsUpload = "अपलोड की गई फ़ाइल आवश्यक" -noSources = "अभी तक कोई स्रोत कनेक्ट नहीं है। इसे इनपुट के रूप में उपयोग करने के लिए एक बनाएँ।" +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "आपकी खोज से कोई टूल मेल नहीं खाते।" -pipelineSettings = "पाइपलाइन सेटिंग्स" +pause = "Pause" +rename = "Rename pipeline" searchTools = "टूल खोजें" -selectToolBody = "अपनी पाइपलाइन बनाने के लिए एक टूल जोड़ें।" -selectToolTitle = "अभी कोई टूल नहीं" sendToSystem = "दूसरे सिस्टम को भेजें" stepsIncompatible = "ये चरण उनके पिछले चरण द्वारा उत्पन्न चीज़ पर नहीं चल सकते: {{tools}}।" stepsNeedSetup = "सेव करने से पहले इन चरणों का सेटअप अभी भी आवश्यक है: {{tools}}।" -toolSettings = "टूल सेटिंग्स" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "अपरिचित operation, जैसा है वैसा रखा गया।" unsavedBody = "आपके पास unsaved बदलाव हैं। बाहर निकलने से पहले उन्हें save करें, या छोड़ दें?" unsavedTitle = "Unsaved बदलाव" -uploadUnsupported = "Uploaded files अभी pipelines में supported नहीं हैं, इसलिए ये steps save नहीं किए जा सकते: {{tools}}." usesDefaults = "Default settings के साथ चलता है" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "पिछले चरण की हर फ़ाइल को जोड़ता है" @@ -7825,31 +7778,36 @@ undeclared-operation = "जाँच नहीं कर सकता कि य [portal.pipelines.composer] addTool = "टूल जोड़ें" -cancel = "रद्द करें" -chainEmpty = "अपनी पाइपलाइन बनाना शुरू करने के लिए एक टूल जोड़ें।" create = "पाइपलाइन बनाएँ" +createPaused = "Create paused" editingUnsupported = "Editing के लिए इन tool params को दिखाना अभी supported नहीं है।" -moveDown = "नीचे ले जाएँ" -moveUp = "ऊपर ले जाएँ" +editSource = "Edit source" name = "नाम" namePlaceholder = "उदा. Redaction sweep" noToolSettings = "इस टूल में कोई configurable settings नहीं हैं।" -operations_one = "Operation ({{count}})" -operations_other = "Operations ({{count}})" output = "Output" -removeStep = "Operation हटाएँ" save = "बदलाव save करें" scheduleEvery = "हर" -sources = "स्रोत" -sourcesLoading = "स्रोत लोड हो रहे हैं..." trigger = "Trigger" triggerManual = "केवल Manual" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "दिन" hours = "घंटे" minutes = "मिनट" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\" हटाएँ? इसे पूर्ववत नहीं किया जा सकता।" cancel = "रद्द करें" @@ -7867,6 +7825,37 @@ connectSource = "स्रोत कनेक्ट करें" description = "अपनी पहली पाइपलाइन बनाएँ: वे स्रोत चुनें जिन पर यह चलती है, operations को chain करें, और output कहाँ जाए चुनें।" title = "अभी कोई पाइपलाइन नहीं" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "सक्रिय" paused = "रोकी गई" @@ -7879,7 +7868,6 @@ completed_one = "Run पूरा हुआ।" completed_other = "सभी {{count}} runs पूरे हुए।" empty = "चलाने के लिए कुछ नहीं: sources में process करने के लिए कोई documents नहीं थे।" failed = "Run विफल रहा: {{error}}" -historyCleared = "इतिहास साफ़ किया गया। अगला रन स्रोतों में वर्तमान में मौजूद सब कुछ फिर से प्रोसेस करेगा।" inFlight = "चलाने के लिए कुछ नया नहीं: दस्तावेज़ अभी भी पहले के रन से प्रोसेस हो रहे हैं।" parked_one = "चलाने के लिए कुछ नहीं: {{count}} दस्तावेज़ पहले विफल हुआ और पार्क किया गया है। कारण ठीक करें, फिर इसे फिर से प्रयास करने के लिए इतिहास साफ़ करें।" parked_other = "चलाने के लिए कुछ नहीं: {{count}} दस्तावेज़ पहले विफल हुए और पार्क किए गए हैं। कारण ठीक करें, फिर इन्हें फिर से प्रयास करने के लिए इतिहास साफ़ करें।" @@ -7895,6 +7883,7 @@ name = "पाइपलाइन" sources = "स्रोत" status = "स्थिति" steps = "Steps" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Folder watch" @@ -8209,6 +8198,24 @@ label = "Jira issue में संलग्न करें" description = "प्रोसेस किए गए दस्तावेज़ को attachment के रूप में भेजता है।" label = "दस्तावेज़ email करें (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "प्रोसेस किए गए दस्तावेज़ को folder में लिखता है।" label = "Nextcloud पर अपलोड करें" @@ -8683,16 +8690,6 @@ maxed = "अधिकतम हो गया" subtitle = "आपका free trial {{date}} तक चलता है। Card आवश्यक नहीं।" title = "Enterprise trial" -[portal.search] -ariaLabel = "खोजें" -placeholder = "Stirling खोजें — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "उपलब्ध होने पर quick actions यहां दिखाई देंगी।" -noActionsTitle = "कोई quick actions नहीं" -noMatches = "\"{{query}}\" के लिए कोई मिलान नहीं" -noMatchesDescription = "कोई अलग keyword आज़माएं या catalogue browse करें।" - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "रद्द करें" confirm = "हटाएं" title = "स्रोत हटाएं?" -[portal.sources.empty] -description = "एक folder (और जल्द ही, cloud storage) कनेक्ट करें ताकि आपकी policies के पास दस्तावेज़ खींचने की जगह हो।" -title = "अभी तक कोई स्रोत कनेक्ट नहीं" - [portal.sources.kpi] inUse = "उपयोग में" total = "Connections" @@ -8785,6 +8778,7 @@ unused = "Unused" documents = "दस्तावेज़" source = "स्रोत" status = "स्थिति" +type = "Type" usedBy = "Policies" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "नीतियाँ और गोपनीयता" [settings.preferences] title = "प्राथमिकताएँ" -[settings.search] -placeholder = "सेटिंग्स पेज खोजें..." - [settings.security] description = "अपने खाते को सुरक्षित रखने के लिए अपना पासवर्ड अपडेट करें।" title = "सुरक्षा" @@ -10128,6 +10119,7 @@ last = "अंतिम पृष्ठ" maintainRatio = "आनुपातिक अनुपात बनाए रखें टॉगल करें" next = "अगला पृष्ठ" noSavedSigs = "कोई सहेजा गया हस्ताक्षर नहीं मिला" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "व्यक्तिगत हस्ताक्षर" previous = "पिछला पृष्ठ" redo = "फिर करें" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "पृष्ठभूमि हटाना व hint = "अपने हस्ताक्षर की PNG या JPG इमेज अपलोड करें" label = "हस्ताक्षर की इमेज अपलोड करें" placeholder = "इमेज फ़ाइल चुनें" +previewAlt = "Current image signature" processing = "छवि संसाधित हो रही है..." removeBackground = "सफेद पृष्ठभूमि हटाएँ (पारदर्शी बनाएँ)" @@ -10179,6 +10172,17 @@ saved = "ऊपर से एक सहेजा हुआ हस्ताक् text = "ऊपर अपना नाम दर्ज करने के बाद, PDF पर कहीं भी क्लिक करके अपना हस्ताक्षर रखें।" title = "हस्ताक्षर कैसे जोड़ें" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "हस्ताक्षर को स्थानांतरित करें" pause = "प्लेसमेंट रोकें" @@ -10625,6 +10629,23 @@ title = "सर्वर पर अपलोड करें" updateButton = "सर्वर पर अपडेट करें" uploadButton = "सर्वर पर अपलोड करें" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "वापस जाएं" pdfTools = "PDF Tools" placeholder = "शुरू करने के लिए कोई टूल चुनें" premiumFeature = "प्रीमियम फीचर:" -searchTools = "टूल खोजें" toolsHeader = "उपकरण" viewAllTools = "सभी उपकरण देखें" @@ -10822,6 +10842,9 @@ removal = "हटाना" signing = "हस्ताक्षर" verification = "सत्यापन" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "कोई टूल नहीं मिला" noTools = "कोई टूल उपलब्ध नहीं" @@ -10979,6 +11002,13 @@ approver = "Policy approve करता है" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "{{email}} को भेजा गया आमंत्रण रद्द करें? वे मौजूदा लिंक से शामिल नहीं हो पाएँगे।" cancelInviteTitle = "आमंत्रण रद्द करें" @@ -10998,10 +11028,8 @@ title = "अभी तक कोई member नहीं" addToTeam = "Team में जोड़ें" guestCount = "{{count}} guest" guests = "Guests" -guestsDesc = "बाहरी collaborators, आपके द्वारा साझा की गई चीज़ों तक सीमित। केवल Editor।" ledBy = "{{owner}} द्वारा नेतृत्व" org = "Organization" -orgDesc = "Org-wide authority और policy approval वाले owners" owners = "{{count}} owner" team = "{{name}} team" teamMeta = "{{count}} लोग" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "{{who}} द्वारा आमंत्रित" cancel = "रद्द करें" -count = "{{count}} लंबित" -desc = "आमंत्रित लोग जो अभी तक शामिल नहीं हुए हैं। वे स्वीकार करने तक एक सीट रखते हैं।" expiresInDays_one = "{{count}} दिन में समाप्त होगा" expiresInDays_other = "{{count}} दिनों में समाप्त होगा" expiresToday = "आज समाप्त होगा" title = "लंबित आमंत्रण" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "backend तक पहुँचने में कुछ गलत हुआ, या आपके पास पहुँच नहीं है। फिर से कोशिश करें।" title = "सदस्यों को लोड नहीं किया जा सका" @@ -11237,12 +11267,15 @@ searchPlaceholder = "संलग्नक खोजें" title = "संलग्नक" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "बुकमार्क शीर्षक" bookmarkTitleRequired = "बुकमार्क शीर्षक आवश्यक है" closeSidebar = "बुकमार्क sidebar बंद करें" collapseAll = "सभी बुकमार्क संक्षिप्त करें" +empty = "No bookmarks in this document" expandAll = "सभी बुकमार्क विस्तृत करें" searchPlaceholder = "बुकमार्क खोजें" +title = "Bookmarks" [viewer.comments] addComment = "टिप्पणी जोड़ें" @@ -11264,10 +11297,12 @@ locateAnnotation = "दस्तावेज़ में ढूँढें" moreActions = "और क्रियाएँ" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "पेज {{page}}" placingHint = "रखने के लिए किसी पृष्ठ पर क्लिक करें… (रद्द करें)" removeCommentOnly = "केवल टिप्पणी हटाएँ" saveReply = "उत्तर सहेजें" +searchPlaceholder = "Search comments" title = "टिप्पणियाँ" typeComment = "टिप्पणी" typeInsertText = "पाठ डालें" @@ -11290,6 +11325,7 @@ unsavedDesc = "आपके पास असहेजे परिवर्त closeSidebar = "layers sidebar बंद करें" hideAll = "सभी layers छिपाएँ" showAll = "सभी layers दिखाएँ" +title = "Layers" [viewer.link] delete = "लिंक हटाएं" @@ -11646,6 +11682,7 @@ exitRedaction = "रिडैक्शन मोड से बाहर नि exportAll = "PDF निर्यात करें" exportSelected = "चयनित पृष्ठ निर्यात करें" formFill = "फ़ॉर्म भरें" +hideToolbar = "Hide toolbar" multiTool = "मल्टी-टूल" panMode = "पैन मोड" print = "PDF प्रिंट करें" @@ -11666,6 +11703,7 @@ selectAll = "सभी चुनें" selectByNumber = "पृष्ठ संख्याओं से चुनें" selectLanguage = "भाषा चुनें" share = "साझा करें" +showToolbar = "Show toolbar" toggleAnnotations = "एनोटेशन की दृश्यता टॉगल करें" toggleAttachments = "संलग्नक टॉगल करें" toggleBookmarks = "बुकमार्क टॉगल करें" diff --git a/frontend/editor/public/locales/hr-HR/translation.toml b/frontend/editor/public/locales/hr-HR/translation.toml index 29dc3b9de6..0b2aa9b82c 100644 --- a/frontend/editor/public/locales/hr-HR/translation.toml +++ b/frontend/editor/public/locales/hr-HR/translation.toml @@ -1,7 +1,6 @@ alphabet = "Abeceda" apply = "Primijeni" applyAndContinue = "Spremi i izađi" -areYouSure = "Jeste li sigurni da želite izaći?" back = "Natrag" black = "Crno" blue = "Plavo" @@ -106,6 +105,7 @@ undoSuccess = "Radnja je uspješno poništena" unknown = "Nepoznato" unpin = "Otkači datoteku (zamijeni nakon pokretanja alata)" unsavedChanges = "Imate nespremljene promjene u svom PDF-u." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Nespremljene promjene" unsupported = "Nepodržano" username = "Korisničko ime" @@ -1879,6 +1879,9 @@ width = "Širina" [app] description = "Besplatna alternativa Adobe Acrobatu (10M+ preuzimanja)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Pretvori u PDF/A-3b" convertToPdfA3bDescription = "Stvara arhivski PDF s ugrađenim privicima" @@ -2933,12 +2936,16 @@ text = "Kako bi ove dozvole bile nepromjenjive, upotrijebite alat Dodaj lozinku [chat.actions] copy = "Kopiraj poruku" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opcije Stirling agenta" +assistant = "AI Assistant" clearChat = "Obriši chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Ova radnja će iskoristiti vaše kredite u oblaku" tooltip = "Odaberi boju sa zaslona" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Odaberite boju" [common] +actions = "Actions" back = "Natrag" cancel = "Odustani" close = "Zatvori" +codeSample = "Code sample" collapse = "Sažmi" confirm = "Potvrdi" continue = "Nastavi" copied = "Kopirano!" copy = "Kopiraj" done = "Gotovo" +enterValue = "Enter value" error = "Pogreška" expand = "Proširi" loading = "Učitavanje..." next = "Sljedeće" +open = "Open" preview = "Pregled" previous = "Prethodno" refresh = "Osvježi" remaining = "Preostalo" retry = "Pokušaj ponovno" save = "Spremi" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Očisti odabrano" @@ -3185,6 +3199,7 @@ title = "Metoda kompresije" [compress.settings] desiredSize = "Željena veličina datoteke" desiredSizePlaceholder = "Unesite veličinu" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Kompresija je jednostavan način smanjenja veličine datoteke. Odaberite Veličinu datoteke za unos ciljne veličine i da mi prilagodimo kvalitetu umjesto vas. Odaberite Kvaliteta za ručno postavljanje jačine kompresije." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF opcije" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Najprije odaberite izvorni format" settings = "Postavke" single = "Jedan" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Pretraži..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Uredi" @@ -3824,11 +3861,13 @@ shareSelected = "Podijeli odabrano" sharing = "Dijeljenje" showAll = "Prikaži sve" showHistory = "Prikaži povijest" +sortBy = "Sort files" sortByDate = "Sortiraj po datumu" sortByName = "Sortiraj po imenu" sortBySize = "Sortiraj po veličini" storage = "Spremište" storageState = "Pohrana" +storageUsed = "Storage used" synced = "Sinkronizirano" title = "Otpremi PDF datoteke" toolChain = "Primijenjeni alati" @@ -3874,8 +3913,12 @@ addFiles = "Dodaj datoteke" addingFiles = "Dodavanje datoteka…" collapse = "Sažmi bočnu traku" customizeGroups = "Prilagodi grupe" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Otvorite datoteke za početak" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Proširi bočnu traku" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive nije konfiguriran" @@ -3888,15 +3931,17 @@ openFromComputer = "Otvori s računala" openSettings = "Otvori postavke" other = "Ostalo" recent = "Nedavno" -search = "Pretraži" -searchPlaceholder = "Pretraži datoteke..." viewAll = "Prikaži svih {{count}} datoteka" [fileSidebar.fileItem] closeViewer = "Zatvori preglednik" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Otvori u pregledniku" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Vrati na zadane vrijednosti" subtitle = "Grupirajte svoje datoteke u nadređene kategorije. Dodajte postojeće ili nove oznake u kategoriju, preimenujte je ili stvorite vlastitu. Datoteke koje nisu ni u jednoj od vaših kategorija prikazuju se pod “Ostalo”." title = "Kategorije bočne trake" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Dodaj u radni prostor" addToWorkspaceCount = "Dodaj {{count}} u radni prostor" @@ -3918,7 +3971,6 @@ backToMyFiles = "Natrag na Moje datoteke" breadcrumbs = "Putanja mape" cancel = "Odustani" classification = "Klasifikacija" -clearSearch = "Očisti pretragu" clearSelection = "Poništi odabir" closeDetails = "Zatvori detalje" create = "Stvori" @@ -3950,6 +4002,7 @@ downloadAll = "Preuzmi sve" downloadVersion = "Preuzmi ovu verziju" dropOverlay = "Ispusti datoteke za prijenos" dropOverlaySub = "Datoteke započinju u Lokalno. Upotrijebite 'Premjesti u' ili 'Spremi u oblak' da ih organizirate u mapu." +duplicate = "Duplicate" file = "Datoteka" fileInfo = "File info" fileMenu = "Radnje nad datotekom" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Promijeni veličinu stabla mapa (strelice, Shift za veće ko save = "Spremi" saveToServer = "Spremi na poslužitelj" saveToServerDisabledHint = "Spremanje na poslužitelj nije omogućeno na ovom poslužitelju. Zamolite administratora da to omogući." -search = "Pretraži" -searchPlaceholder = "Pretraži ovu mapu i podmape" selectAll = "Odaberi sve" selectAllHint = "Kliknite za odabir svega. Savjet: držite Ctrl (ili Cmd) za dodavanje datoteka jednu po jednu, Shift za odabir raspona." selectedCount = "{{count}} odabrano" @@ -4044,6 +4095,10 @@ title = "Još nema datoteka u oblaku" hint = "Datoteke spremljene bez prijenosa ostaju ovdje. Ispustite datoteku da biste je dodali." title = "Nema lokalnih datoteka" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Datoteke koje otvorite ili uredite pojavit će se ovdje." title = "Još ništa nije izmijenjeno" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Nije moguće izbrisati mapu." deleteFolderFailedDetail = "Nije moguće izbrisati mapu: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Nije moguće ažurirati izgled mape." folderAppearanceFailedDetail = "Nije moguće ažurirati izgled mape: {{message}}" moveFilesFailed = "Nije moguće premjestiti datoteke." @@ -4113,6 +4170,11 @@ localHint = "Pohranjeno samo u ovom pregledniku" shared = "Dijeljeno" sharedHint = "Podijeljeno s vama putem veze" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Najstarije prvo" modifiedDesc = "Najnovije prvo" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Nema datoteka u pohrani. Najprije otvorite neke datoteke open = "Otvori" openFile = "Otvori datoteku" openFiles = "Otvori datoteke" +selectFile = "Select {{name}}" selectFromStorage = "Odaberite iz spremišta" upload = "Otpremi" uploadFile = "Otpremi datoteku" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Sve spremljeno" +analyzingFields = "Analysing form fields..." extractCsvError = "Izdvajanje CSV-a nije uspjelo" extractXlsxError = "Izdvajanje XLSX-a nije uspjelo" +filled = "filled" flattenAfterFilling = "Izravnaj nakon ispunjavanja" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obv" requiredFieldsError = "Ispunite sva obavezna polja" rescanFields = "Ponovno skeniraj polja" @@ -4795,9 +4863,6 @@ title = "Slika u PDF" [imageToPdf] tags = "konverzija,pretvaranje,img,jpg,slika,foto" -[infoBanner] -dismiss = "Odbaci" - [invite] acceptError = "Neuspjelo stvaranje računa" accountFor = "Stvaranje računa za" @@ -4979,6 +5044,34 @@ uploadSuccess = "Otpremanje uspješno!" uploadSuccessMessage = "Vaše slike su prenesene." validating = "Provjera sesije..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Skenirajte za otpremu fotografija. Slike se automatski pretvaraju u PDF." descriptionNoConvert = "Skenirajte za otpremu fotografija s mobilnog uređaja." @@ -4997,8 +5090,13 @@ title = "Otpremanje s mobitela" tags = "Višestruki alat, više operacija, korisničko sučelje, povlačenje klikom, prednji kraj, strana klijenta, interaktivno, nepopravljivo, pomicanje" title = "PDF Višenamjenski alat" -[navbar] -search = "Pretraži" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Autentikacija nije bila uspješna. Možete zatvoriti ovaj prozor i pokušati ponovno." @@ -5127,6 +5225,7 @@ activeFiles = "Prikaz Aktivne datoteke pokazuje sve PDF-ove koj allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Zatvori" cropSettings = "Sada kada smo odabrali datoteku koju želimo obrezati, možemo konfigurirati alat Obrezivanje i odabrati područje na koje želimo obrezati PDF." +dialogLabel = "Onboarding" fileCheckbox = "Klikom na jednu od datoteka odabirete je za obradu. Možete odabrati više datoteka za skupne radnje." fileReplacement = "Izmijenjena datoteka automatski će zamijeniti izvornu datoteku na Radnoj ploči, što vam omogućuje da je lako proslijedite kroz još alata." filesButton = "Gumb Datoteke na traci brzog pristupa omogućuje učitavanje PDF-ova na koje ćete primijeniti alate." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF u PDF/A" tags = "arhiva,dugoročno,standardno,konverzija,čuvanje,čuvanje" title = "PDF u PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "tisak,standard,pretvorba,produkcija,priprema za tisak,arhiva" title = "PDF u PDF/X" @@ -6045,6 +6151,7 @@ small = "500 kredita" xsmall = "100 kredita" [plan.availablePlans] +currency = "Billing currency" subtitle = "Odaberite plan koji odgovara vašim potrebama" title = "Dostupni planovi" @@ -6624,6 +6731,7 @@ reachedTitle = "Dosegnuto je mjesečno ograničenje potrošnje" title = "Nije moguće otvoriti Stripe portal" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "od {{allowance}} besplatnih PDF-ova iskorišteno" capSuffix_other = "od {{allowance}} besplatnih PDF-ova iskorišteno" eyebrow = "Probno razdoblje za Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Pošaljite dokument ili obavijest emailom kada se pravilo pokrene." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Spremite obrađene dokumente u Nextcloud." @@ -7077,11 +7211,6 @@ title = "Službeni SDK-ovi" beta = "Beta" deprecated = "Zastarjelo" -[portal.docs.search] -empty = "Nema odgovarajuće dokumentacije" -placeholder = "Pretraži dokumentaciju" -results = "{{count}} rezultata" - [portal.docs.skills] eyebrow = "VJEŠTINE" lead = "Ugrađene, imenovane sposobnosti koje vaš agent poziva kao jedan alat. Svaka vještina je deterministički lanac operacija s priloženim evaluacijama." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Nijedan dokument ne odgovara ovom filtru." rowActions = "Radnje retka" sensitiveLabel = "Osjetljivo" -sensitiveTitle = "Osjetljivo — potreban je pristup" [portal.documents.table.columns] action = "Pipeline / Radnja" document = "Dokument" +labels = "Labels" product = "Proizvod" status = "Status" time = "Vrijeme" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Zadnji put viđeno" region = "Regija" status = "Status" +target = "Target" version = "Verzija" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Ovaj prikaz naišao je na neočekivanu pogrešku. Pokušajte pono retry = "Pokušaj ponovno" title = "Nešto je pošlo po zlu na ovoj stranici" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Natrag" body = "Preuzmite desktop aplikaciju ili samostalno hostirajte poslužitelj." @@ -7380,11 +7560,6 @@ cancel = "Odustani" confirm = "Opozovi ključ" title = "Opozovi API ključ" -[portal.infrastructure.attestationLabel] -attested = "Potvrđeno" -inScope = "U opsegu" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtriraj revizijske događaje prema kategoriji" heading = "Revizijski zapisi" @@ -7456,11 +7631,6 @@ info = "Info" success = "Uspjeh" warning = "Upozorenje" -[portal.infrastructure.certLabel] -certified = "Certificirano" -inProgress = "U tijeku" -notStarted = "Nije započeto" - [portal.infrastructure.createKey] cancel = "Odustani" createKey = "Izradi ključ" @@ -7474,238 +7644,10 @@ subtitleCreated = "Kopirajte ovu tajnu sada — neće se ponovno prikazati." title = "Izradi API ključ" titleCreated = "Ključ izrađen" -[portal.infrastructure.deployLabel] -live = "Aktivno" -queued = "U redu čekanja" -rolledBack = "Vraćeno" -rolling = "Uvođenje u tijeku" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Implementirao" -environment = "Okruženje" -product = "Proizvod" -status = "Status" -version = "Verzija" -when = "Kada" - -[portal.infrastructure.deployments.recent] -heading = "Nedavne implementacije" -subheading = "Najnovija uvođenja kroz proizvode i okruženja." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instance" -latency = "Latencija" -load = "Opterećenje" -p99 = "P99" -region = "Regija" -status = "Status" -throughput = "Propusnost" -uptime = "Vrijeme rada" -version = "Verzija" - -[portal.infrastructure.deployments.regions] -heading = "Regije" -subheading = "Živo zdravlje za svaku implementiranu Stirling regiju — latencija, opterećenje i verzija uvođenja." - -[portal.infrastructure.deployments.regions.empty] -description = "Implementirane regije pojavit će se ovdje nakon što vaš radni prostor bude provisioniran." -title = "Nema implementiranih regija" - [portal.infrastructure.keyLabel] active = "Aktivno" revoked = "Opozvano" -[portal.infrastructure.modelLabel] -active = "Aktivno" -degraded = "Smanjena učinkovitost" -disabled = "Onemogućeno" - -[portal.infrastructure.models] -heading = "Modeli" -msValue = "{{value}} ms" -subheading = "Katalog modela i usmjeravanje koje pokreće obradu dokumenata u vašem radnom prostoru." - -[portal.infrastructure.models.byom] -description = "Registrirajte on-prem ili samostalno hostani model i vežite ga uz regiju za obradu vezanu uz rezidenciju podataka." -title = "Donesite vlastiti model" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Upravljani modeli dostupni vašem radnom prostoru, s latencijom i troškom uživo." -subEnterprise = "Upravljani, vlastiti i on-prem modeli — s dostupnim vezivanjem po regiji." - -[portal.infrastructure.models.catalogue.empty] -description = "Modeli u katalogu vašeg radnog prostora pojavit će se ovdje." -title = "Nema dostupnih modela" - -[portal.infrastructure.models.columns] -cost = "Trošak" -latency = "Latencija" -load = "Opterećenje" -model = "Model" -status = "Status" -type = "Vrsta" -version = "Verzija" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/poziv" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktivni modeli" -avgLatency = "Prosj. latencija" -included = "Uključeno" -monthlySpend = "Mjesečna potrošnja za modele" - -[portal.infrastructure.models.routing] -empty = "Nema konfiguriranih pravila usmjeravanja." -heading = "Pravila usmjeravanja" -sub = "Koji model obrađuje svaku operaciju. Zadano se primjenjuje kada ne odgovara nijedno uže pravilo." -subLocked = "Usmjerite operacije na određene modele — dostupno na plaćenim planovima." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Nadogradite na Pro kako biste kontrolirali koji model obrađuje svaku operaciju i vrstu dokumenta." -title = "Usmjeravanje modela je plaćena značajka" - -[portal.infrastructure.models.routingColumns] -default = "Zadano" -docType = "Vrsta dokumenta" -modelForAria = "Model za {{operation}}" -operation = "Operacija" -routedTo = "Usmjereno na" - -[portal.infrastructure.modelTypeLabel] -classification = "Klasifikacija" -extraction = "Izdvajanje" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Smanjena učinkovitost" -down = "Nedostupno" -healthy = "Ispravno" - -[portal.infrastructure.security.access.byok] -description = "Dostavite ključ iz vlastitog KMS. Stirling njime šifrira, ali i dalje može čitati." -label = "Koristite vlastiti ključ (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Ključevi nikada ne napuštaju vaš KMS. Stirling drži samo šifrirani tekst." -label = "Držite vlastiti ključ (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling upravlja ključevima za šifriranje. Najjednostavnije — bez operacija s ključevima s vaše strane." -label = "Ključevi koje drži Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Pravilo pristupa dokumentima" -subheading = "Kontrolira tko može dešifrirati obrađene dokumente u mirovanju." - -[portal.infrastructure.security.attestations] -heading = "Atestacije usklađenosti" -noReport = "Nema dostupnog izvješća" -subheading = "Revizijski položaj po okvirima, s izvješćima dostupnima za atestirane kontrole." -viewReport = "Prikaži izvješće →" - -[portal.infrastructure.security.compliance] -heading = "Usklađenost" -subheading = "Atestacije i certifikati koji pokrivaju Stirling platformu." - -[portal.infrastructure.security.empty] -description = "Sigurnosna konfiguracija vašeg radnog prostora pojavit će se ovdje." -title = "Sigurnosni položaj nije dostupan" - -[portal.infrastructure.security.hyokBanner] -description = "Uz HYOK, ključevi za šifriranje nikada ne napuštaju vaš KMS. Stirling pohranjuje i obrađuje samo šifrirani tekst koji možete opozvati u bilo kojem trenutku." -title = "Stirling ne može dešifrirati vaše dokumente" - -[portal.infrastructure.security.ipAllowlist] -empty = "Nema konfiguriranih IP raspona — sve IP adrese su dopuštene." -heading = "IP allowlista" -sub = "API pristup ograničen je na ove CIDR raspone." -subLocked = "Ograničite API pristup na poznate IP raspone — dostupno na plaćenim planovima." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Nadogradite na Pro kako biste ograničili API pristup na određene mreže." -title = "IP allowlista je plaćena značajka" - -[portal.infrastructure.security.ipColumns] -added = "Dodano" -addedBy = "Dodao" -cidr = "CIDR" -label = "Oznaka" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritam" -heading = "Upravljanje ključevima za šifriranje" -keyId = "Identifikator ključa" -lastRotated = "Zadnja rotacija" -rotateKey = "Rotiraj ključ" -rotationPolicy = "Pravilo rotacije" -subheading = "Skrbništvo nad ključevima koji šifriraju dokumente u mirovanju — tko može dešifrirati i kako se ključevi rotiraju." - -[portal.infrastructure.security.managedBanner] -description = "Skrbništvo Bring-your-own-key (BYOK) i hold-your-own-key (HYOK) dostupno je na Enterprise planu. Nadogradite kako biste dostavili ključeve iz vlastitog KMS." -title = "Ključevima na vašem planu upravlja Stirling" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Azija i Pacifik" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR granica podataka" -label = "Europska unija" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Sjedinjene Američke Države" - -[portal.infrastructure.security.residencyHeader] -heading = "Rezidencija podataka" -subheading = "Gdje se dokumenti pohranjuju i obrađuju." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} iskorišteno" - -[portal.infrastructure.storage.empty] -description = "Povezana pohrana i korištenje pojavit će se ovdje." -title = "Nema konfigurirane pohrane" - -[portal.infrastructure.storage.lifecycle] -active = "Aktivno" -activeRange = "0–{{value}}d" -archived = "Arhivirano" -coldStorage = "hladna pohrana" -deleted = "Izbrisano" -never = "nikad" -purged = "trajno izbrisano" - -[portal.infrastructure.storage.providers] -connect = "Poveži" -connected = "Povezano" -heading = "Povezani pružatelji" -subheading = "Gdje se zapisuju obrađeni artefakti." - -[portal.infrastructure.storage.retention] -heading = "Zadržavanje" -subheading = "Koliko se dugo artefakti čuvaju prije brisanja u životnom ciklusu." -windowLabel = "Zadani prozor zadržavanja" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dan" -days_other = "{{count}} dana" -never = "Nikad ne briši" - -[portal.infrastructure.storage.totalUsage] -heading = "Ukupno korištenje" -progressLabel = "Iskorištena pohrana" -subheading = "Pohrana iskorištena kroz sve povezane pružatelje." - [portal.infrastructure.tabs] apiKeys = "API ključevi" audit = "Revizijski zapisi" @@ -7715,11 +7657,9 @@ security = "Sigurnost" storage = "Pohrana" [portal.integrations] -addAnother = "Dodaj još jednu" availableHeading = "Dostupno" comingSoonHeading = "Uskoro" connect = "Poveži" -connectedHeading = "Povezano" connectionCount_one = "{{count}} veza" connectionCount_other = "{{count}} veza" customApi = "Prilagođeni API" @@ -7737,6 +7677,10 @@ security = "Sigurnost" signing = "Potpisivanje" storage = "Pohrana" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Povezano" @@ -7767,7 +7711,6 @@ integrations = "Integracije" pipelines = "Pipelineovi" policies = "Pravila" procurement = "Nabava" -settings = "Postavke" sources = "Izvori" usage = "Korištenje i naplata" users = "Korisnici" @@ -7784,7 +7727,7 @@ title = "Pipelineovi" newPipeline = "Novi pipeline" [portal.pipelines.builder] -addStep = "Dodaj alat" +activate = "Activate" back = "Natrag na pipelineove" cannotFollow = "Ne može primiti {{produced}}" chooseAccount = "Odaberite račun" @@ -7792,28 +7735,38 @@ chooseDestination = "Odaberite odredište" chooseOperation = "Odaberite što ovaj korak radi" chooseSource = "Odaberite izvor" discard = "Odbaci promjene" -enabled = "Omogućeno" inputs = "Ulaz" inputSource = "Ulazni izvor" inputTrigger = "Okidač" keepEditing = "Nastavi uređivati" +moreActions = "More actions" needsConfiguring = "Potrebno je postavljanje" -needsUpload = "Potrebna je učitana datoteka" -noSources = "Još nema povezanih izvora. Izradite jedan da biste ga koristili kao ulaz." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Nijedan alat ne odgovara vašem pretraživanju." -pipelineSettings = "Postavke pipelinea" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Pretraži alate" -selectToolBody = "Dodajte alat za izradu pipelinea." -selectToolTitle = "Još nema alata" sendToSystem = "Pošalji u drugi sustav" stepsIncompatible = "Ovi se koraci ne mogu pokrenuti na onome što proizvodi njihov prethodni korak: {{tools}}." stepsNeedSetup = "Ove korake još treba postaviti prije spremanja: {{tools}}." -toolSettings = "Postavke alata" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Neprepoznata operacija, zadržana takva kakva jest." unsavedBody = "Imate nespremljene promjene. Spremiti ih prije odlaska ili ih odbaciti?" unsavedTitle = "Nespremljene promjene" -uploadUnsupported = "Učitane datoteke još nisu podržane u pipelineovima, pa se ovi koraci ne mogu spremiti: {{tools}}." usesDefaults = "Pokreće se sa zadanim postavkama" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kombinira svaku datoteku iz prethodnog koraka" @@ -7825,31 +7778,36 @@ undeclared-operation = "Nije moguće provjeriti što ovaj korak prihvaća" [portal.pipelines.composer] addTool = "Dodaj alat" -cancel = "Odustani" -chainEmpty = "Dodajte alat za početak izrade pipelinea." create = "Izradi pipeline" +createPaused = "Create paused" editingUnsupported = "Prikaz ovih parametara alata za uređivanje još nije podržan." -moveDown = "Pomakni dolje" -moveUp = "Pomakni gore" +editSource = "Edit source" name = "Naziv" namePlaceholder = "npr. Pregled redakcija" noToolSettings = "Ovaj alat nema konfigurabilnih postavki." -operations_one = "Operacija ({{count}})" -operations_other = "Operacije ({{count}})" output = "Izlaz" -removeStep = "Ukloni operaciju" save = "Spremi promjene" scheduleEvery = "Pokreni svakih" -sources = "Izvori" -sourcesLoading = "Učitavanje izvora..." trigger = "Okidač" triggerManual = "Samo ručno" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dana" hours = "sati" minutes = "minuta" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Izbrisati \"{{name}}\"? Ovo se ne može poništiti." cancel = "Odustani" @@ -7867,6 +7825,37 @@ connectSource = "Poveži izvor" description = "Izradite svoj prvi pipeline: odaberite izvore nad kojima se pokreće, povežite operacije i odaberite kamo ide izlaz." title = "Još nema pipelineova" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktivno" paused = "Pauzirano" @@ -7879,7 +7868,6 @@ completed_one = "Pokretanje dovršeno." completed_other = "Svih {{count}} pokretanja je dovršeno." empty = "Nema ništa za pokretanje: izvori nisu imali dokumente za obradu." failed = "Pokretanje nije uspjelo: {{error}}" -historyCleared = "Povijest je očišćena. Sljedeće pokretanje ponovno obrađuje sve što je trenutačno u izvorima." inFlight = "Nema ničeg novog za pokretanje: dokumenti se još obrađuju iz ranijeg pokretanja." parked_one = "Nema ničega za pokretanje: {{count}} dokument prethodno nije uspio i parkiran je. Ispravite uzrok, zatim očistite povijest da biste ga ponovno pokušali obraditi." parked_other = "Nema ničega za pokretanje: {{count}} dokumenata prethodno nije uspjelo i parkirano je. Ispravite uzrok, zatim očistite povijest da biste ih ponovno pokušali obraditi." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Izvori" status = "Status" steps = "Koraci" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Nadzor mape" @@ -8209,6 +8198,24 @@ label = "Priloži na Jira zadatak" description = "Šalje obrađeni dokument kao privitak." label = "Pošalji dokument emailom (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Zapisuje obrađeni dokument u mapu." label = "Prenesi na Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Dosegnut maksimum" subtitle = "Vaše besplatno probno razdoblje traje do {{date}}. Kartica nije potrebna." title = "Enterprise probno razdoblje" -[portal.search] -ariaLabel = "Pretraživanje" -placeholder = "Pretražite Stirling — endpoints, pipelines, dokumente…" - -[portal.search.empty] -noActionsDescription = "Brze radnje pojavit će se ovdje kada budu dostupne." -noActionsTitle = "Nema brzih radnji" -noMatches = "Nema podudaranja za \"{{query}}\"" -noMatchesDescription = "Pokušajte s drugom ključnom riječi ili pregledajte katalog." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Odustani" confirm = "Izbriši" title = "Izbrisati izvor?" -[portal.sources.empty] -description = "Povežite mapu (a uskoro i cloud storage) kako bi vaša pravila imala odakle povlačiti dokumente." -title = "Još nema povezanih izvora" - [portal.sources.kpi] inUse = "U upotrebi" total = "Veze" @@ -8785,6 +8778,7 @@ unused = "Neiskorišteno" documents = "Dokumenti" source = "Izvor" status = "Status" +type = "Type" usedBy = "Pravila" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Pravila i privatnost" [settings.preferences] title = "Preferencije" -[settings.search] -placeholder = "Pretraži stranice postavki..." - [settings.security] description = "Ažurirajte svoju lozinku kako biste osigurali račun." title = "Sigurnost" @@ -10128,6 +10119,7 @@ last = "Zadnja stranica" maintainRatio = "Uključi/isključi održavanje omjera" next = "Sljedeća stranica" noSavedSigs = "Nema sacuvanih potpisa pronađenih" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Osobni potpisi" previous = "Prethodna stranica" redo = "Ponovi" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Uklanjanje pozadine nije uspjelo" hint = "Učitajte PNG ili JPG sliku svojega potpisa" label = "Učitaj sliku potpisa" placeholder = "Odaberite slikovnu datoteku" +previewAlt = "Current image signature" processing = "Obrada slike..." removeBackground = "Ukloni bijelu pozadinu (učini prozirnom)" @@ -10179,6 +10172,17 @@ saved = "Odaberite spremljeni potpis iznad, zatim kliknite bilo gdje na PDF da b text = "Nakon što iznad unesete svoje ime, kliknite bilo gdje na PDF-u da postavite svoj potpis." title = "Kako dodati potpis" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Pomakni potpis" pause = "Pauziraj postavljanje" @@ -10625,6 +10629,23 @@ title = "Prenesi na poslužitelj" updateButton = "Ažuriraj na poslužitelju" uploadButton = "Prenesi na poslužitelj" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Vrati se" pdfTools = "PDF alati" placeholder = "Odaberite alat za početak" premiumFeature = "Premium značajka:" -searchTools = "Pretraži alate" toolsHeader = "Alati" viewAllTools = "Prikaži sve alate" @@ -10822,6 +10842,9 @@ removal = "Uklanjanje" signing = "Potpisivanje" verification = "Provjera" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nije pronađen nijedan alat" noTools = "Nema dostupnih alata" @@ -10979,6 +11002,13 @@ approver = "Odobrava pravilo" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Otkazati pozivnicu za {{email}}? Neće se moći pridružiti putem trenutačne poveznice." cancelInviteTitle = "Otkaži pozivnicu" @@ -10998,10 +11028,8 @@ title = "Još nema članova" addToTeam = "Dodaj u tim" guestCount = "{{count}} gost" guests = "Gosti" -guestsDesc = "Vanjski suradnici, ograničeni na ono što ste podijelili. Samo Editor." ledBy = "vodi {{owner}}" org = "Organizacija" -orgDesc = "Vlasnici s ovlastima na razini organizacije i odobravanjem pravila" owners = "{{count}} vlasnik" team = "{{name}} tim" teamMeta = "{{count}} osoba" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Poziv poslao/la {{who}}" cancel = "Otkaži" -count = "{{count}} na čekanju" -desc = "Pozvane osobe koje se još nisu pridružile. Zauzimaju mjesto dok ne prihvate." expiresInDays_one = "Istječe za {{count}} dan" expiresInDays_other = "Istječe za {{count}} dana" expiresToday = "Istječe danas" title = "Pozivnice na čekanju" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Nešto je pošlo po zlu pri povezivanju s pozadinskim sustavom ili nemate pristup. Pokušajte ponovno." title = "Nije moguće učitati članove" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Pretraži priloge" title = "Prilozi" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Naslov knjižne oznake" bookmarkTitleRequired = "Naslov knjižne oznake je obavezan" closeSidebar = "Zatvori bočnu traku knjižnih oznaka" collapseAll = "Sažmi sve knjižne oznake" +empty = "No bookmarks in this document" expandAll = "Proširi sve knjižne oznake" searchPlaceholder = "Pretraži knjižne oznake" +title = "Bookmarks" [viewer.comments] addComment = "Dodaj komentar" @@ -11264,10 +11297,12 @@ locateAnnotation = "Pronađi u dokumentu" moreActions = "Više radnji" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Stranica {{page}}" placingHint = "Kliknite stranicu za postavljanje… (odustani)" removeCommentOnly = "Ukloni samo komentar" saveReply = "Spremi odgovor" +searchPlaceholder = "Search comments" title = "Komentari" typeComment = "Komentar" typeInsertText = "Umetni tekst" @@ -11290,6 +11325,7 @@ unsavedDesc = "Imate nespremljene promjene" closeSidebar = "Zatvori bočnu traku slojeva" hideAll = "Sakrij sve slojeve" showAll = "Prikaži sve slojeve" +title = "Layers" [viewer.link] delete = "Izbriši poveznicu" @@ -11646,6 +11682,7 @@ exitRedaction = "Izađi iz načina zatamnjivanja" exportAll = "Izvezi PDF" exportSelected = "Izvezi odabrane stranice" formFill = "Ispuni obrazac" +hideToolbar = "Hide toolbar" multiTool = "Višenamjenski alat" panMode = "Način pomicanja" print = "Ispis PDF-a" @@ -11666,6 +11703,7 @@ selectAll = "Odaberi sve" selectByNumber = "Odaberi po brojevima stranica" selectLanguage = "Odaberi jezik" share = "Dijeli" +showToolbar = "Show toolbar" toggleAnnotations = "Uključi/isključi vidljivost anotacija" toggleAttachments = "Uključi/isključi privitke" toggleBookmarks = "Uključi/isključi oznake" diff --git a/frontend/editor/public/locales/hu-HU/translation.toml b/frontend/editor/public/locales/hu-HU/translation.toml index 30f75b7b72..cebcbeb964 100644 --- a/frontend/editor/public/locales/hu-HU/translation.toml +++ b/frontend/editor/public/locales/hu-HU/translation.toml @@ -1,7 +1,6 @@ alphabet = "ABC" apply = "Alkalmaz" applyAndContinue = "Mentés és kilépés" -areYouSure = "Biztosan elhagyja az oldalt?" back = "Vissza" black = "Fekete" blue = "Kék" @@ -106,6 +105,7 @@ undoSuccess = "A művelet sikeresen visszavonva" unknown = "Ismeretlen" unpin = "Rögzítés feloldása (csere az eszköz futtatása után)" unsavedChanges = "Mentetlen változtatásai vannak a PDF-en." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Mentetlen változtatások" unsupported = "Nem támogatott" username = "Felhasználónév" @@ -1879,6 +1879,9 @@ width = "Szélesség" [app] description = "Az ingyenes Adobe Acrobat alternatíva (10M+ letöltés)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Átalakítás PDF/A-3b formátumba" convertToPdfA3bDescription = "Beágyazott mellékleteket tartalmazó archiválási PDF-et hoz létre" @@ -2933,12 +2936,16 @@ text = "Ezeket a jogosultságokat tegye megváltoztathatatlanná a \"Jelszó hoz [chat.actions] copy = "Üzenet másolása" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling ügynök beállításai" +assistant = "AI Assistant" clearChat = "Csevegés törlése" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Ez a művelet a felhőkrediteket használja" tooltip = "Szín kiválasztása a képernyőről" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Szín kiválasztása" [common] +actions = "Actions" back = "Vissza" cancel = "Mégse" close = "Bezárás" +codeSample = "Code sample" collapse = "Összecsukás" confirm = "Megerősítés" continue = "Folytatás" copied = "Másolva!" copy = "Másolás" done = "Kész" +enterValue = "Enter value" error = "Hiba" expand = "Kibontás" loading = "Betöltés..." next = "Következő" +open = "Open" preview = "Előnézet" previous = "Előző" refresh = "Frissítés" remaining = "Hátralévő" retry = "Újra" save = "Mentés" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Kijelöltek törlése" @@ -3185,6 +3199,7 @@ title = "Tömörítési mód" [compress.settings] desiredSize = "Kívánt fájlméret" desiredSizePlaceholder = "Méret megadása" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "A tömörítés egyszerű módja a fájlméret csökkentésének. Válassza a Fájlméret lehetőséget a célméret megadásához, és mi ehhez igazítjuk a minőséget. Válassza a Minőség lehetőséget a tömörítés erősségének kézi beállításához." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF beállítások" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Először válasszon forrásformátumot" settings = "Beállítások" single = "Egyetlen" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Keresés..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Szerkesztés" @@ -3824,11 +3861,13 @@ shareSelected = "Kijelöltek megosztása" sharing = "Megosztás" showAll = "Összes megjelenítése" showHistory = "Előzmények megjelenítése" +sortBy = "Sort files" sortByDate = "Rendezés dátum szerint" sortByName = "Rendezés név szerint" sortBySize = "Rendezés méret szerint" storage = "Tárhely" storageState = "Tárhely" +storageUsed = "Storage used" synced = "Szinkronizálva" title = "PDF fájlok feltöltése" toolChain = "Alkalmazott eszközök" @@ -3874,8 +3913,12 @@ addFiles = "Fájlok hozzáadása" addingFiles = "Fájlok hozzáadása…" collapse = "Oldalsáv összecsukása" customizeGroups = "Csoportok testreszabása" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "A kezdéshez nyisson meg fájlokat" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Oldalsáv kinyitása" googleDrive = "Google Drive" googleDriveDisabled = "A Google Drive nincs beállítva" @@ -3888,15 +3931,17 @@ openFromComputer = "Megnyitás a számítógépről" openSettings = "Beállítások megnyitása" other = "Egyéb" recent = "Legutóbbi" -search = "Keresés" -searchPlaceholder = "Fájlok keresése..." viewAll = "Mind a(z) {{count}} fájl megtekintése" [fileSidebar.fileItem] closeViewer = "Megjelenítő bezárása" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Megnyitás a megjelenítőben" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Alapértelmezések visszaállítása" subtitle = "Csoportosítsa fájljait szülőkategóriákba. Adjon meglévő vagy új címkéket egy kategóriához, nevezze át, vagy hozzon létre sajátot. Azok a fájlok, amelyek egyik kategóriájába sem tartoznak, az „Egyéb” alatt jelennek meg." title = "Oldalsáv kategóriái" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Hozzáadás a munkaterülethez" addToWorkspaceCount = "{{count}} hozzáadása a munkaterülethez" @@ -3918,7 +3971,6 @@ backToMyFiles = "Vissza a Saját fájlokhoz" breadcrumbs = "Mappaútvonal" cancel = "Mégse" classification = "Besorolás" -clearSearch = "Keresés törlése" clearSelection = "Kijelölés törlése" closeDetails = "Részletek bezárása" create = "Létrehozás" @@ -3950,6 +4002,7 @@ downloadAll = "Összes letöltése" downloadVersion = "E verzió letöltése" dropOverlay = "Húzza ide a fájlokat a feltöltéshez" dropOverlaySub = "A fájlok a Helyi fülön jelennek meg. Használja az 'Áthelyezés' vagy a 'Mentés felhőbe' lehetőséget a mappába rendezéshez." +duplicate = "Duplicate" file = "Fájl" fileInfo = "File info" fileMenu = "Fájlműveletek" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Mappafa átméretezése (nyilak, Shift a nagyobb lépésekhe save = "Mentés" saveToServer = "Mentés a szerverre" saveToServerDisabledHint = "A mentés a szerverre ezen a szerveren nincs engedélyezve. Kérje meg az adminisztrátort, hogy engedélyezze." -search = "Keresés" -searchPlaceholder = "Keresés ebben a mappában és almappáiban" selectAll = "Összes kijelölése" selectAllHint = "Kattintson az összes kijelöléséhez. Tipp: tartsa lenyomva a Ctrl-t (vagy Cmd-et) az egyenkénti hozzáadáshoz, a Shiftet a tartomány kijelöléséhez." selectedCount = "{{count}} kijelölve" @@ -4044,6 +4095,10 @@ title = "Még nincsenek felhőfájlok" hint = "A feltöltés nélkül mentett fájlok itt maradnak. Húzzon ide egy fájlt a hozzáadáshoz." title = "Nincsenek csak helyi fájlok" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Az Ön által megnyitott vagy szerkesztett fájlok itt jelennek meg." title = "Még semmi sem módosult" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Nem sikerült a mappát törölni." deleteFolderFailedDetail = "Nem sikerült törölni a mappát: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Nem sikerült módosítani a mappa megjelenését." folderAppearanceFailedDetail = "Nem sikerült módosítani a mappa megjelenését: {{message}}" moveFilesFailed = "Nem sikerült áthelyezni a fájlokat." @@ -4113,6 +4170,11 @@ localHint = "Csak ebben a böngészőben tárolva" shared = "Megosztott" sharedHint = "Önnel linken keresztül megosztva" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Legrégebbi elöl" modifiedDesc = "Legújabb elöl" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Nincsenek fájlok a tárhelyen. Először nyisson meg n open = "Megnyitás" openFile = "Fájl megnyitása" openFiles = "Fájlok megnyitása" +selectFile = "Select {{name}}" selectFromStorage = "Kiválasztás a tárhelyről" upload = "Feltöltés" uploadFile = "Fájl feltöltése" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Minden mentve" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV kinyerése sikertelen" extractXlsxError = "XLSX kinyerése sikertelen" +filled = "filled" flattenAfterFilling = "Kitöltés után lapítás" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "kötelező" requiredFieldsError = "Kérjük, töltse ki az összes kötelező mezőt" rescanFields = "Mezők újraolvasása" @@ -4795,9 +4863,6 @@ title = "Kép PDF-be" [imageToPdf] tags = "konverzió,kép,jpg,fotó,fénykép" -[infoBanner] -dismiss = "Bezárás" - [invite] acceptError = "Nem sikerült létrehozni a fiókot" accountFor = "Fiók létrehozása ehhez:" @@ -4979,6 +5044,34 @@ uploadSuccess = "Sikeres feltöltés!" uploadSuccessMessage = "A képei átkerültek." validating = "Munkamenet ellenőrzése..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Szkenneljen a fotók feltöltéséhez. A képek automatikusan PDF-be konvertálódnak." descriptionNoConvert = "Szkenneljen a fotók feltöltéséhez a mobileszközéről." @@ -4997,8 +5090,13 @@ title = "Feltöltés mobilról" tags = "Többfunkciós eszköz,Több művelet,UI,húzd és ejtsd,frontend,kliens oldali,interaktív,kezelhető,mozgatás" title = "PDF többfunkciós eszköz" -[navbar] -search = "Keresés" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "A hitelesítés nem volt sikeres. Bezárhatja ezt az ablakot, és megpróbálhatja újra." @@ -5127,6 +5225,7 @@ activeFiles = "Az Aktív fájlok nézet megmutatja az összes o allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Bezárás" cropSettings = "Most, hogy kiválasztottuk a vágandó fájlt, beállíthatjuk a Vágás eszközt, hogy megadjuk a PDF kivágandó területét." +dialogLabel = "Onboarding" fileCheckbox = "Egy fájlra kattintva kijelöli azt feldolgozásra. Több fájlt is kijelölhet kötegelt műveletekhez." fileReplacement = "A módosított fájl automatikusan felülírja az eredetit a Munkaterületen, így könnyen további eszközökön futtathatja." filesButton = "A Gyors hozzáférés sávon lévő Fájlok gombbal tölthet fel PDF-eket az eszközök használatához." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF konvertálása PDF/A formátumba" tags = "archívum,hosszú távú,szabvány,konvertálás,tárolás,megőrzés" title = "PDF konvertálása PDF/A formátumba" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "nyomtatás,szabvány,átalakítás,gyártás,előnyomdai,archiválás" title = "PDF PDF/X-re" @@ -6045,6 +6151,7 @@ small = "500 kredit" xsmall = "100 kredit" [plan.availablePlans] +currency = "Billing currency" subtitle = "Válassza ki az igényeinek megfelelő csomagot" title = "Elérhető csomagok" @@ -6624,6 +6731,7 @@ reachedTitle = "Havi költési limit elérve" title = "Nem sikerült megnyitni a Stripe portált" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "felhasználva a(z) {{allowance}} ingyenes PDF-ből" capSuffix_other = "felhasználva a(z) {{allowance}} ingyenes PDF-ből" eyebrow = "Processor próbaidőszak" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Dokumentum vagy értesítés küldése emailben, amikor egy szabály fut." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Feldolgozott dokumentumok elhelyezése a Nextcloudban." @@ -7077,11 +7211,6 @@ title = "Hivatalos SDK-k" beta = "Beta" deprecated = "Elavult" -[portal.docs.search] -empty = "Nincs egyező dokumentáció" -placeholder = "Dokumentáció keresése" -results = "{{count}} találat" - [portal.docs.skills] eyebrow = "KÉSZSÉGEK" lead = "Csomagolt, névvel ellátott képességek, amelyeket az ügynöke egyetlen eszközként hív meg. Minden készség determinisztikus műveletlánc, csatolt evalokkal." @@ -7180,11 +7309,11 @@ editorAction = "Szerkesztő" empty = "Egy dokumentum sem felel meg ennek a szűrőnek." rowActions = "Sorműveletek" sensitiveLabel = "Érzékeny" -sensitiveTitle = "Érzékeny — hozzáférés szükséges" [portal.documents.table.columns] action = "Pipeline / művelet" document = "Dokumentum" +labels = "Labels" product = "Termék" status = "Állapot" time = "Idő" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Utoljára látható" region = "Régió" status = "Állapot" +target = "Target" version = "Verzió" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Ez a nézet váratlan hibába ütközött. Próbálja újra, vagy retry = "Próbálja újra" title = "Valami hiba történt ezen az oldalon" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Vissza" body = "Töltse le az asztali alkalmazást, vagy üzemeltesse saját maga a szervert." @@ -7380,11 +7560,6 @@ cancel = "Mégse" confirm = "Kulcs visszavonása" title = "API-kulcs visszavonása" -[portal.infrastructure.attestationLabel] -attested = "Hitelesítve" -inScope = "Hatókörben" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Audit események szűrése kategória szerint" heading = "Auditnaplók" @@ -7456,11 +7631,6 @@ info = "Információ" success = "Sikeres" warning = "Figyelmeztetés" -[portal.infrastructure.certLabel] -certified = "Tanúsított" -inProgress = "Folyamatban" -notStarted = "Nincs elkezdve" - [portal.infrastructure.createKey] cancel = "Mégse" createKey = "Kulcs létrehozása" @@ -7474,238 +7644,10 @@ subtitleCreated = "Másolja ki most ezt a titkot — többé nem jelenik meg." title = "API kulcs létrehozása" titleCreated = "Kulcs létrehozva" -[portal.infrastructure.deployLabel] -live = "Élő" -queued = "Sorban áll" -rolledBack = "Visszagörgetve" -rolling = "Kiterítés folyamatban" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/perc" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Telepítette" -environment = "Környezet" -product = "Termék" -status = "Állapot" -version = "Verzió" -when = "Mikor" - -[portal.infrastructure.deployments.recent] -heading = "Legutóbbi telepítések" -subheading = "A legújabb bevezetések termékek és környezetek szerint." - -[portal.infrastructure.deployments.regionColumns] -instances = "Példányok" -latency = "Késleltetés" -load = "Terhelés" -p99 = "P99" -region = "Régió" -status = "Állapot" -throughput = "Átbocsátás" -uptime = "Üzemidő" -version = "Verzió" - -[portal.infrastructure.deployments.regions] -heading = "Régiók" -subheading = "Élő állapot minden telepített Stirling régióhoz — késleltetés, terhelés és bevezetési verzió." - -[portal.infrastructure.deployments.regions.empty] -description = "A telepített régiók itt jelennek meg, amint a munkaterülete ki van építve." -title = "Nincsenek telepített régiók" - [portal.infrastructure.keyLabel] active = "Aktív" revoked = "Visszavonva" -[portal.infrastructure.modelLabel] -active = "Aktív" -degraded = "Leromlott" -disabled = "Letiltva" - -[portal.infrastructure.models] -heading = "Modellek" -msValue = "{{value}} ms" -subheading = "A modellkatalógus és az útválasztás, amely a dokumentumfeldolgozást működteti a munkaterületén." - -[portal.infrastructure.models.byom] -description = "Regisztráljon egy on-prem vagy self-hosted modellt, és rögzítse egy régióhoz adatrezidencia-kötött feldolgozáshoz." -title = "Hozza saját modelljét" - -[portal.infrastructure.models.catalogue] -heading = "Katalógus" -sub = "A munkaterületéhez elérhető kezelt modellek élő késleltetéssel és költséggel." -subEnterprise = "Kezelt, saját és on-prem modellek — régiónkénti rögzítéssel." - -[portal.infrastructure.models.catalogue.empty] -description = "A munkaterület katalógusában lévő modellek itt jelennek meg." -title = "Nincsenek elérhető modellek" - -[portal.infrastructure.models.columns] -cost = "Költség" -latency = "Késleltetés" -load = "Terhelés" -model = "Modell" -status = "Állapot" -type = "Típus" -version = "Verzió" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/hívás" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktív modellek" -avgLatency = "Átlagos késleltetés" -included = "Tartalmazza" -monthlySpend = "Havi modellköltség" - -[portal.infrastructure.models.routing] -empty = "Nincsenek konfigurált útválasztási szabályok." -heading = "Útválasztási szabályok" -sub = "Melyik modell kezeli az egyes műveleteket. Az alapértelmezett akkor érvényes, ha nincs szűkebb egyező szabály." -subLocked = "Műveletek irányítása adott modellekhez — fizetős csomagokban érhető el." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Frissítsen Pro csomagra annak szabályozásához, hogy melyik modell kezelje az egyes műveleteket és dokumentumtípusokat." -title = "A modellútválasztás fizetős funkció" - -[portal.infrastructure.models.routingColumns] -default = "Alapértelmezett" -docType = "Dokumentumtípus" -modelForAria = "Modell ehhez: {{operation}}" -operation = "Művelet" -routedTo = "Ide irányítva" - -[portal.infrastructure.modelTypeLabel] -classification = "Osztályozás" -extraction = "Kinyerés" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Leromlott" -down = "Nem elérhető" -healthy = "Egészséges" - -[portal.infrastructure.security.access.byok] -description = "Adjon meg egy kulcsot a saját KMS-éből. A Stirling ezzel titkosít, de továbbra is olvasni tud." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "A kulcsok soha nem hagyják el a KMS-ét. A Stirling csak titkosított szöveget tárol." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "A Stirling kezeli a titkosítási kulcsokat. A legegyszerűbb — nincs kulcsművelet az Ön oldalán." -label = "Stirling által tárolt kulcsok" - -[portal.infrastructure.security.accessPolicy] -heading = "Dokumentum-hozzáférési szabályzat" -subheading = "Szabályozza, hogy ki fejtheti vissza a feldolgozott dokumentumokat nyugalmi állapotban." - -[portal.infrastructure.security.attestations] -heading = "Megfelelőségi tanúsítások" -noReport = "Nincs elérhető jelentés" -subheading = "Keretrendszerenkénti auditállapot, jelentésekkel a tanúsított kontrollokról." -viewReport = "Jelentés megtekintése →" - -[portal.infrastructure.security.compliance] -heading = "Megfelelőség" -subheading = "A Stirling platformot lefedő tanúsítások és minősítések." - -[portal.infrastructure.security.empty] -description = "A munkaterület biztonsági konfigurációja itt fog megjelenni." -title = "A biztonsági állapot nem érhető el" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK esetén a titkosítási kulcsok soha nem hagyják el a KMS-ét. A Stirling csak olyan titkosított szöveget tárol és dolgoz fel, amelyet bármikor visszavonhat." -title = "A Stirling nem tudja visszafejteni a dokumentumait" - -[portal.infrastructure.security.ipAllowlist] -empty = "Nincsenek konfigurált IP tartományok — minden IP engedélyezett." -heading = "IP allowlist" -sub = "Az API hozzáférés ezekre a CIDR tartományokra van korlátozva." -subLocked = "API hozzáférés korlátozása ismert IP tartományokra — fizetős csomagokban érhető el." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Frissítsen Pro csomagra az API hozzáférés adott hálózatokra korlátozásához." -title = "Az IP allowlist fizetős funkció" - -[portal.infrastructure.security.ipColumns] -added = "Hozzáadva" -addedBy = "Hozzáadta" -cidr = "CIDR" -label = "Címke" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritmus" -heading = "Titkosítási kulcsok kezelése" -keyId = "Kulcsazonosító" -lastRotated = "Utolsó rotálás" -rotateKey = "Kulcs rotálása" -rotationPolicy = "Rotálási szabályzat" -subheading = "A dokumentumokat nyugalmi állapotban titkosító kulcsok felügyelete — ki fejthet vissza, és hogyan rotálódnak a kulcsok." - -[portal.infrastructure.security.managedBanner] -description = "A bring-your-own-key (BYOK) és hold-your-own-key (HYOK) kulcsfelügyelet Enterprise csomagban érhető el. Frissítsen, hogy kulcsokat adjon meg a saját KMS-éből." -title = "A kulcsokat a Stirling kezeli az Ön csomagjában" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Ázsia és Csendes-óceáni térség" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR adatperem" -label = "Európai Unió" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Egyesült Államok" - -[portal.infrastructure.security.residencyHeader] -heading = "Adatrezidencia" -subheading = "Ahol a dokumentumok tárolása és feldolgozása történik." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} felhasználva" - -[portal.infrastructure.storage.empty] -description = "A csatlakoztatott tárhely és használat itt jelenik meg." -title = "Nincs konfigurált tárhely" - -[portal.infrastructure.storage.lifecycle] -active = "Aktív" -activeRange = "0–{{value}} nap" -archived = "Archiválva" -coldStorage = "hideg tároló" -deleted = "Törölve" -never = "soha" -purged = "véglegesen törölve" - -[portal.infrastructure.storage.providers] -connect = "Csatlakozás" -connected = "Csatlakoztatva" -heading = "Csatlakoztatott szolgáltatók" -subheading = "Ahol a feldolgozott artefaktumok íródnak." - -[portal.infrastructure.storage.retention] -heading = "Megőrzés" -subheading = "Meddig maradnak meg az artefaktumok az életciklus szerinti törlés előtt." -windowLabel = "Alapértelmezett megőrzési időszak" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} nap" -days_other = "{{count}} nap" -never = "Soha ne törölje" - -[portal.infrastructure.storage.totalUsage] -heading = "Teljes használat" -progressLabel = "Felhasznált tárhely" -subheading = "Az összes csatlakoztatott szolgáltatónál felhasznált tárhely." - [portal.infrastructure.tabs] apiKeys = "API kulcsok" audit = "Auditnaplók" @@ -7715,11 +7657,9 @@ security = "Biztonság" storage = "Tárhely" [portal.integrations] -addAnother = "Másik hozzáadása" availableHeading = "Elérhető" comingSoonHeading = "Hamarosan" connect = "Csatlakoztatás" -connectedHeading = "Csatlakoztatva" connectionCount_one = "{{count}} kapcsolat" connectionCount_other = "{{count}} kapcsolat" customApi = "Egyéni API" @@ -7737,6 +7677,10 @@ security = "Biztonság" signing = "Aláírás" storage = "Tárolás" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Csatlakoztatva" @@ -7767,7 +7711,6 @@ integrations = "Integrációk" pipelines = "Pipeline-ok" policies = "Szabályzatok" procurement = "Beszerzés" -settings = "Beállítások" sources = "Források" usage = "Használat és számlázás" users = "Felhasználók" @@ -7784,7 +7727,7 @@ title = "Pipeline-ok" newPipeline = "Új pipeline" [portal.pipelines.builder] -addStep = "Eszköz hozzáadása" +activate = "Activate" back = "Vissza a pipeline-okhoz" cannotFollow = "Nem tudja fogadni ezt: {{produced}}" chooseAccount = "Válasszon fiókot" @@ -7792,28 +7735,38 @@ chooseDestination = "Válasszon célhelyet" chooseOperation = "Válassza ki, mit végezzen ez a lépés" chooseSource = "Válasszon forrást" discard = "Módosítások elvetése" -enabled = "Engedélyezve" inputs = "Bemenet" inputSource = "Bemeneti forrás" inputTrigger = "Trigger" keepEditing = "Szerkesztés folytatása" +moreActions = "More actions" needsConfiguring = "Beállítás szükséges" -needsUpload = "Feltöltött fájl szükséges" -noSources = "Még nincs csatlakoztatott forrás. Hozzon létre egyet, hogy bemenetként használhassa." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Nincs a keresésnek megfelelő eszköz." -pipelineSettings = "Pipeline beállításai" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Eszközök keresése" -selectToolBody = "Adjon hozzá egy eszközt a pipeline felépítéséhez." -selectToolTitle = "Még nincsenek eszközök" sendToSystem = "Küldés másik rendszerbe" stepsIncompatible = "Ezek a lépések nem futtathatók azon, amit az előző lépés előállít: {{tools}}." stepsNeedSetup = "Ezeket a lépéseket mentés előtt még be kell állítani: {{tools}}." -toolSettings = "Eszköz beállításai" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Ismeretlen művelet, változatlanul megtartva." unsavedBody = "Nem mentett módosításai vannak. Mentse őket kilépés előtt, vagy elveti őket?" unsavedTitle = "Nem mentett módosítások" -uploadUnsupported = "A feltöltött fájlok még nem támogatottak pipeline-okban, ezért ezek a lépések nem menthetők: {{tools}}." usesDefaults = "Alapértelmezett beállításokkal fut" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Az előző lépés összes fájlját egyesíti" @@ -7825,31 +7778,36 @@ undeclared-operation = "Nem ellenőrizhető, hogy ez a lépés mit fogad el" [portal.pipelines.composer] addTool = "Eszköz hozzáadása" -cancel = "Mégse" -chainEmpty = "Adjon hozzá egy eszközt a pipeline építésének megkezdéséhez." create = "Pipeline létrehozása" +createPaused = "Create paused" editingUnsupported = "Ezeknek az eszközparamétereknek a szerkesztésre való megjelenítése még nem támogatott." -moveDown = "Mozgatás le" -moveUp = "Mozgatás fel" +editSource = "Edit source" name = "Név" namePlaceholder = "pl. Kitakarítási átvizsgálás" noToolSettings = "Ennek az eszköznek nincsenek konfigurálható beállításai." -operations_one = "Művelet ({{count}})" -operations_other = "Műveletek ({{count}})" output = "Kimenet" -removeStep = "Művelet eltávolítása" save = "Módosítások mentése" scheduleEvery = "Futtatás minden" -sources = "Források" -sourcesLoading = "Források betöltése..." trigger = "Trigger" triggerManual = "Csak manuális" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "nap" hours = "óra" minutes = "perc" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Törli ezt: \"{{name}}\"? Ez nem vonható vissza." cancel = "Mégse" @@ -7867,6 +7825,37 @@ connectSource = "Forrás csatlakoztatása" description = "Hozza létre első pipeline-ját: válassza ki a forrásokat, amelyeken fut, fűzze össze a műveleteket, és adja meg, hová kerüljön a kimenet." title = "Még nincsenek pipeline-ok" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktív" paused = "Szüneteltetve" @@ -7879,7 +7868,6 @@ completed_one = "Futtatás befejezve." completed_other = "Mind a(z) {{count}} futtatás befejeződött." empty = "Nincs mit futtatni: a forrásoknak nem voltak feldolgozandó dokumentumaik." failed = "A futtatás sikertelen: {{error}}" -historyCleared = "Az előzmények törölve. A következő futtatás újrafeldolgoz mindent, ami jelenleg a forrásokban van." inFlight = "Nincs új futtatnivaló: a dokumentumok feldolgozása még folyamatban van egy korábbi futtatásból." parked_one = "Nincs mit futtatni: {{count}} dokumentum korábban meghiúsult és félre lett téve. Javítsa ki az okot, majd törölje az előzményeket az újrapróbálkozáshoz." parked_other = "Nincs mit futtatni: {{count}} dokumentum korábban meghiúsult és félre lett téve. Javítsa ki az okot, majd törölje az előzményeket az újrapróbálkozáshoz." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Források" status = "Állapot" steps = "Lépések" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Mappafigyelés" @@ -8209,6 +8198,24 @@ label = "Csatolás Jira issue-hoz" description = "Elküldi a feldolgozott dokumentumot mellékletként." label = "Dokumentum küldése e-mailben (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "A feldolgozott dokumentumot egy mappába írja." label = "Feltöltés Nextcloudba" @@ -8683,16 +8690,6 @@ maxed = "Elérte a maximumot" subtitle = "Az ingyenes próbaidőszak {{date}} dátumig tart. Kártya nem szükséges." title = "Enterprise próbaidőszak" -[portal.search] -ariaLabel = "Keresés" -placeholder = "Keresés a Stirlingben — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "A gyorsműveletek itt jelennek meg, amint elérhetők." -noActionsTitle = "Nincsenek gyorsműveletek" -noMatches = "Nincs találat erre: \"{{query}}\"" -noMatchesDescription = "Próbáljon másik kulcsszót, vagy böngéssze a katalógust." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Mégse" confirm = "Törlés" title = "Törli a forrást?" -[portal.sources.empty] -description = "Csatlakoztasson egy mappát (és hamarosan felhőtárhelyet), hogy a házirendeknek legyen honnan dokumentumokat beolvasniuk." -title = "Még nincs csatlakoztatott forrás" - [portal.sources.kpi] inUse = "Használatban" total = "Kapcsolatok" @@ -8785,6 +8778,7 @@ unused = "Nem használt" documents = "Dokumentumok" source = "Forrás" status = "Állapot" +type = "Type" usedBy = "Házirendek" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Irányelvek és adatvédelem" [settings.preferences] title = "Beállítások" -[settings.search] -placeholder = "Keresés a beállítások között..." - [settings.security] description = "Frissítse jelszavát fiókja biztonságáért." title = "Biztonság" @@ -10128,6 +10119,7 @@ last = "Utolsó oldal" maintainRatio = "Képarány fenntartása váltása" next = "Következő oldal" noSavedSigs = "Nincsenek mentett aláírások" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Személyes aláírások" previous = "Előző oldal" redo = "Újra" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "A háttér eltávolítása nem sikerült" hint = "Töltse fel az aláírását tartalmazó PNG vagy JPG képet" label = "Aláíráskép feltöltése" placeholder = "Képfájl kiválasztása" +previewAlt = "Current image signature" processing = "Kép feldolgozása..." removeBackground = "Fehér háttér eltávolítása (átlátszóvá tétel)" @@ -10179,6 +10172,17 @@ saved = "Válasszon ki fent egy mentett aláírást, majd kattintson a PDF bárm text = "Miután megadta a nevét fent, kattintson bárhová a PDF-ben az aláírás elhelyezéséhez." title = "Hogyan adjon hozzá aláírást" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Aláírás mozgatása" pause = "Elhelyezés szüneteltetése" @@ -10625,6 +10629,23 @@ title = "Feltöltés a szerverre" updateButton = "Frissítés a szerveren" uploadButton = "Feltöltés a szerverre" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Vissza" pdfTools = "PDF-eszközök" placeholder = "Válasszon egy eszközt a kezdéshez" premiumFeature = "Prémium funkció:" -searchTools = "Eszközök keresése" toolsHeader = "Eszközök" viewAllTools = "Összes eszköz megtekintése" @@ -10822,6 +10842,9 @@ removal = "Eltávolítás" signing = "Aláírás" verification = "Ellenőrzés" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nem találhatók eszközök" noTools = "Nem állnak rendelkezésre eszközök" @@ -10979,6 +11002,13 @@ approver = "Jóváhagyja a házirendet" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Visszavonja a(z) {{email}} címre küldött meghívást? A jelenlegi hivatkozással nem fog tudni csatlakozni." cancelInviteTitle = "Meghívás visszavonása" @@ -10998,10 +11028,8 @@ title = "Még nincsenek tagok" addToTeam = "Hozzáadás csapathoz" guestCount = "{{count}} vendég" guests = "Vendégek" -guestsDesc = "Külső együttműködők, a megosztott tartalomra korlátozva. Csak Editor." ledBy = "vezeti: {{owner}}" org = "Szervezet" -orgDesc = "Tulajdonosok szervezetszintű jogosultsággal és házirend-jóváhagyással" owners = "{{count}} tulajdonos" team = "{{name}} csapat" teamMeta = "{{count}} ember" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Meghívta: {{who}}" cancel = "Visszavonás" -count = "{{count}} függőben" -desc = "Meghívott személyek, akik még nem csatlakoztak. Helyet foglalnak, amíg el nem fogadják." expiresInDays_one = "{{count}} nap múlva lejár" expiresInDays_other = "{{count}} nap múlva lejár" expiresToday = "Ma lejár" title = "Függőben lévő meghívások" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Hiba történt a backend elérésekor, vagy nincs hozzáférésed. Próbáld újra." title = "Nem sikerült betölteni a tagokat" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Mellékletek keresése" title = "Mellékletek" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Könyvjelző címe" bookmarkTitleRequired = "A könyvjelző címe kötelező" closeSidebar = "Könyvjelzők oldalsávjának bezárása" collapseAll = "Összes könyvjelző összecsukása" +empty = "No bookmarks in this document" expandAll = "Összes könyvjelző kibontása" searchPlaceholder = "Könyvjelzők keresése" +title = "Bookmarks" [viewer.comments] addComment = "Megjegyzés hozzáadása" @@ -11264,10 +11297,12 @@ locateAnnotation = "Megkeresés a dokumentumban" moreActions = "További műveletek" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Oldal {{page}}" placingHint = "Kattintson egy oldalra az elhelyezéshez… (mégse)" removeCommentOnly = "Csak a megjegyzés eltávolítása" saveReply = "Válasz mentése" +searchPlaceholder = "Search comments" title = "Megjegyzések" typeComment = "Megjegyzés" typeInsertText = "Szöveg beszúrása" @@ -11290,6 +11325,7 @@ unsavedDesc = "Mentetlen módosításai vannak" closeSidebar = "Rétegek oldalsávjának bezárása" hideAll = "Összes réteg elrejtése" showAll = "Összes réteg megjelenítése" +title = "Layers" [viewer.link] delete = "Hivatkozás törlése" @@ -11646,6 +11682,7 @@ exitRedaction = "Kilépés a kitakarási módból" exportAll = "PDF exportálása" exportSelected = "Kijelölt oldalak exportálása" formFill = "Űrlap kitöltése" +hideToolbar = "Hide toolbar" multiTool = "Többfunkciós eszköz" panMode = "Mozgatás mód" print = "PDF nyomtatása" @@ -11666,6 +11703,7 @@ selectAll = "Összes kijelölése" selectByNumber = "Kiválasztás oldalszámok szerint" selectLanguage = "Nyelv kiválasztása" share = "Megosztás" +showToolbar = "Show toolbar" toggleAnnotations = "Annotációk láthatóságának be-/kikapcsolása" toggleAttachments = "Mellékletek be-/kikapcsolása" toggleBookmarks = "Könyvjelzők be-/kikapcsolása" diff --git a/frontend/editor/public/locales/id-ID/translation.toml b/frontend/editor/public/locales/id-ID/translation.toml index de785b819d..5b3a35918a 100644 --- a/frontend/editor/public/locales/id-ID/translation.toml +++ b/frontend/editor/public/locales/id-ID/translation.toml @@ -1,7 +1,6 @@ alphabet = "Abjad" apply = "Terapkan" applyAndContinue = "Simpan & Keluar" -areYouSure = "Apakah Anda yakin ingin keluar?" back = "Kembali" black = "Hitam" blue = "Biru" @@ -106,6 +105,7 @@ undoSuccess = "Operasi berhasil dibatalkan" unknown = "Tidak diketahui" unpin = "Lepas Sematan File (ganti setelah alat dijalankan)" unsavedChanges = "Anda memiliki perubahan yang belum disimpan pada PDF Anda." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Perubahan Belum Disimpan" unsupported = "Tidak didukung" username = "Nama pengguna" @@ -1879,6 +1879,9 @@ width = "Lebar" [app] description = "Alternatif Adobe Acrobat Gratis (10M+ Unduhan)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Konversi ke PDF/A-3b" convertToPdfA3bDescription = "Membuat PDF arsip dengan lampiran tersemat" @@ -2933,12 +2936,16 @@ text = "Agar izin ini tidak dapat diubah, gunakan alat Tambah Kata Sandi untuk m [chat.actions] copy = "Salin pesan" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opsi agen Stirling" +assistant = "AI Assistant" clearChat = "Hapus obrolan" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Operasi ini akan menggunakan kredit cloud Anda" tooltip = "Pilih warna dari layar" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Pilih warna" [common] +actions = "Actions" back = "Kembali" cancel = "Batal" close = "Tutup" +codeSample = "Code sample" collapse = "Ciutkan" confirm = "Konfirmasi" continue = "Lanjut" copied = "Disalin!" copy = "Salin" done = "Selesai" +enterValue = "Enter value" error = "Error" expand = "Perluas" loading = "Memuat..." next = "Berikutnya" +open = "Open" preview = "Pratinjau" previous = "Sebelumnya" refresh = "Muat Ulang" remaining = "Tersisa" retry = "Coba Lagi" save = "Simpan" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Bersihkan yang dipilih" @@ -3185,6 +3199,7 @@ title = "Metode Kompresi" [compress.settings] desiredSize = "Ukuran file yang diinginkan" desiredSizePlaceholder = "Masukkan ukuran" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Kompresi adalah cara mudah untuk mengurangi ukuran file. Pilih Ukuran File untuk memasukkan ukuran target dan biarkan kami menyesuaikan kualitas untuk Anda. Pilih Kualitas untuk mengatur kekuatan kompresi secara manual." @@ -3387,6 +3402,24 @@ pdfOptions = "Opsi PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Pilih format sumber terlebih dahulu" settings = "Pengaturan" single = "Tunggal" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Cari..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Edit" @@ -3824,11 +3861,13 @@ shareSelected = "Bagikan yang Dipilih" sharing = "Berbagi" showAll = "Tampilkan Semua" showHistory = "Tampilkan Riwayat" +sortBy = "Sort files" sortByDate = "Urutkan berdasarkan Tanggal" sortByName = "Urutkan berdasarkan Nama" sortBySize = "Urutkan berdasarkan Ukuran" storage = "Penyimpanan" storageState = "Penyimpanan" +storageUsed = "Storage used" synced = "Tersinkron" title = "Unggah File PDF" toolChain = "Alat yang Diterapkan" @@ -3874,8 +3913,12 @@ addFiles = "Tambahkan file" addingFiles = "Menambahkan file…" collapse = "Ciutkan bilah samping" customizeGroups = "Sesuaikan grup" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Buka file untuk memulai" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Perluas bilah samping" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive belum dikonfigurasi" @@ -3888,15 +3931,17 @@ openFromComputer = "Buka dari komputer" openSettings = "Buka pengaturan" other = "Lainnya" recent = "Terbaru" -search = "Cari" -searchPlaceholder = "Cari file..." viewAll = "Lihat semua {{count}} file" [fileSidebar.fileItem] closeViewer = "Tutup penampil" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Buka di penampil" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Reset ke default" subtitle = "Kelompokkan file Anda ke dalam kategori induk. Tambahkan label yang sudah ada atau label baru ke kategori, ganti namanya, atau buat sendiri. File yang tidak termasuk dalam kategori mana pun akan muncul di “Lainnya”." title = "Kategori sidebar" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Tambahkan ke ruang kerja" addToWorkspaceCount = "Tambahkan {{count}} ke ruang kerja" @@ -3918,7 +3971,6 @@ backToMyFiles = "Kembali ke File Saya" breadcrumbs = "Jalur folder" cancel = "Batal" classification = "Klasifikasi" -clearSearch = "Hapus pencarian" clearSelection = "Hapus pilihan" closeDetails = "Tutup detail" create = "Buat" @@ -3950,6 +4002,7 @@ downloadAll = "Unduh semua" downloadVersion = "Unduh versi ini" dropOverlay = "Lepaskan file untuk mengunggah" dropOverlaySub = "File dimulai di Lokal. Gunakan 'Pindahkan ke' atau 'Simpan ke cloud' untuk mengaturnya ke dalam folder." +duplicate = "Duplicate" file = "File" fileInfo = "File info" fileMenu = "Tindakan file" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Ubah ukuran pohon folder (tombol panah, Shift untuk langkah save = "Simpan" saveToServer = "Simpan ke server" saveToServerDisabledHint = "Menyimpan ke server tidak diaktifkan di server ini. Minta administrator Anda untuk mengaktifkannya." -search = "Cari" -searchPlaceholder = "Cari folder ini & subfoldernya" selectAll = "Pilih semua" selectAllHint = "Klik untuk memilih semua. Tip: tahan Ctrl (atau Cmd) untuk menambah file satu per satu, Shift untuk memilih rentang." selectedCount = "{{count}} dipilih" @@ -4044,6 +4095,10 @@ title = "Belum ada file cloud" hint = "File yang disimpan tanpa mengunggah tetap di sini. Lepaskan file untuk menambahkannya." title = "Tidak ada file khusus lokal" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "File yang Anda buka atau edit akan muncul di sini." title = "Belum ada yang diubah" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Tidak dapat menghapus folder." deleteFolderFailedDetail = "Tidak dapat menghapus folder: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Tidak dapat memperbarui tampilan folder." folderAppearanceFailedDetail = "Tidak dapat memperbarui tampilan folder: {{message}}" moveFilesFailed = "Tidak dapat memindahkan file." @@ -4113,6 +4170,11 @@ localHint = "Hanya disimpan di peramban ini" shared = "Dibagikan" sharedHint = "Dibagikan kepada Anda melalui tautan" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Terlama dulu" modifiedDesc = "Terbaru dulu" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Tidak ada file di penyimpanan. Buka beberapa file terleb open = "Buka" openFile = "Buka File" openFiles = "Buka File" +selectFile = "Select {{name}}" selectFromStorage = "Pilih dari Penyimpanan" upload = "Unggah" uploadFile = "Unggah File" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Semua tersimpan" +analyzingFields = "Analysing form fields..." extractCsvError = "Gagal mengekstrak CSV" extractXlsxError = "Gagal mengekstrak XLSX" +filled = "filled" flattenAfterFilling = "Ratakan setelah mengisi" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "wajib" requiredFieldsError = "Harap isi semua bidang wajib" rescanFields = "Pindai ulang bidang" @@ -4795,9 +4863,6 @@ title = "Gambar ke PDF" [imageToPdf] tags = "konversi,img,jpg,gambar,foto" -[infoBanner] -dismiss = "Tutup" - [invite] acceptError = "Gagal membuat akun" accountFor = "Membuat akun untuk" @@ -4979,6 +5044,34 @@ uploadSuccess = "Unggah Berhasil!" uploadSuccessMessage = "Gambar Anda telah ditransfer." validating = "Memvalidasi sesi..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Pindai untuk mengunggah foto. Gambar otomatis dikonversi ke PDF." descriptionNoConvert = "Pindai untuk mengunggah foto dari perangkat seluler Anda." @@ -4997,8 +5090,13 @@ title = "Unggah dari Ponsel" tags = "Alat multi,Operasi multi,UI,klik seret,front end,sisi klien,interaktif,sulit diatur,pindah" title = "Alat Multi PDF" -[navbar] -search = "Cari" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Autentikasi tidak berhasil. Anda dapat menutup jendela ini dan mencoba lagi." @@ -5127,6 +5225,7 @@ activeFiles = "Tampilan Active Files menampilkan semua PDF yang allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Tutup" cropSettings = "Sekarang setelah kita memilih file yang ingin dipotong, kita dapat mengonfigurasi alat Crop untuk memilih area yang ingin kita potong pada PDF." +dialogLabel = "Onboarding" fileCheckbox = "Mengklik salah satu file akan memilihnya untuk diproses. Anda dapat memilih beberapa file untuk operasi batch." fileReplacement = "File yang telah diubah akan menggantikan file asli di Workbench secara otomatis, sehingga Anda dapat dengan mudah menjalankannya melalui lebih banyak alat." filesButton = "Tombol Files pada bilah Akses Cepat memungkinkan Anda mengunggah PDF untuk menggunakan alat." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF ke PDF/A" tags = "arsip, jangka panjang, standar, konversi, penyimpanan, pelestarian" title = "PDF Ke PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "cetak,standar,konversi,produksi,pracetak,arsip" title = "PDF ke PDF/X" @@ -6045,6 +6151,7 @@ small = "500 Kredit" xsmall = "100 Kredit" [plan.availablePlans] +currency = "Billing currency" subtitle = "Pilih paket yang sesuai kebutuhan Anda" title = "Paket yang Tersedia" @@ -6624,6 +6731,7 @@ reachedTitle = "Batas pengeluaran bulanan tercapai" title = "Tidak dapat membuka portal Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "dari {{allowance}} PDF gratis digunakan" capSuffix_other = "dari {{allowance}} PDF gratis digunakan" eyebrow = "Uji coba Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Kirim email dokumen atau notifikasi saat kebijakan berjalan." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Simpan dokumen yang diproses ke Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDK resmi" beta = "Beta" deprecated = "Deprecated" -[portal.docs.search] -empty = "Tidak ada dokumentasi yang cocok" -placeholder = "Cari dokumentasi" -results = "{{count}} hasil" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Kemampuan bernama yang dibundel dan dipanggil agen Anda sebagai satu alat. Setiap skill adalah rantai operasi deterministik dengan evaluasi terlampir." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Tidak ada dokumen yang cocok dengan filter ini." rowActions = "Tindakan baris" sensitiveLabel = "Sensitif" -sensitiveTitle = "Sensitif — akses diperlukan" [portal.documents.table.columns] action = "Pipeline / Tindakan" document = "Dokumen" +labels = "Labels" product = "Produk" status = "Status" time = "Waktu" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Terakhir terlihat" region = "Region" status = "Status" +target = "Target" version = "Versi" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Tampilan ini mengalami error tak terduga. Coba lagi, atau pilih b retry = "Coba lagi" title = "Terjadi kesalahan pada halaman ini" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Kembali" body = "Unduh aplikasi desktop atau self-host server." @@ -7380,11 +7560,6 @@ cancel = "Batal" confirm = "Cabut key" title = "Cabut API key" -[portal.infrastructure.attestationLabel] -attested = "Teratestasi" -inScope = "Dalam cakupan" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filter peristiwa audit berdasarkan kategori" heading = "Log audit" @@ -7456,11 +7631,6 @@ info = "Info" success = "Berhasil" warning = "Peringatan" -[portal.infrastructure.certLabel] -certified = "Tersertifikasi" -inProgress = "Sedang berlangsung" -notStarted = "Belum dimulai" - [portal.infrastructure.createKey] cancel = "Batal" createKey = "Buat kunci" @@ -7474,238 +7644,10 @@ subtitleCreated = "Salin rahasia ini sekarang — tidak akan ditampilkan lagi." title = "Buat API key" titleCreated = "Kunci dibuat" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "Dalam antrean" -rolledBack = "Di-rollback" -rolling = "Sedang diluncurkan" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Diterapkan oleh" -environment = "Environment" -product = "Produk" -status = "Status" -version = "Versi" -when = "Kapan" - -[portal.infrastructure.deployments.recent] -heading = "Penerapan terbaru" -subheading = "Rollout terbaru di seluruh produk dan environment." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instans" -latency = "Latensi" -load = "Beban" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Throughput" -uptime = "Uptime" -version = "Versi" - -[portal.infrastructure.deployments.regions] -heading = "Region" -subheading = "Kesehatan langsung untuk setiap region Stirling yang diterapkan — latensi, beban, dan versi rollout." - -[portal.infrastructure.deployments.regions.empty] -description = "Region yang diterapkan muncul di sini setelah workspace Anda disediakan." -title = "Tidak ada region yang diterapkan" - [portal.infrastructure.keyLabel] active = "Aktif" revoked = "Dicabut" -[portal.infrastructure.modelLabel] -active = "Aktif" -degraded = "Menurun" -disabled = "Dinonaktifkan" - -[portal.infrastructure.models] -heading = "Model" -msValue = "{{value}} ms" -subheading = "Katalog model dan routing yang mendukung pemrosesan dokumen di seluruh workspace Anda." - -[portal.infrastructure.models.byom] -description = "Daftarkan model on-prem atau yang dihosting sendiri dan pin ke region untuk pemrosesan terikat residensi data." -title = "Bawa model Anda sendiri" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Model terkelola yang tersedia untuk workspace Anda, dengan latensi dan biaya langsung." -subEnterprise = "Model terkelola, bawa-sendiri, dan on-prem — dengan pinning per region tersedia." - -[portal.infrastructure.models.catalogue.empty] -description = "Model dalam katalog workspace Anda muncul di sini." -title = "Tidak ada model tersedia" - -[portal.infrastructure.models.columns] -cost = "Biaya" -latency = "Latensi" -load = "Beban" -model = "Model" -status = "Status" -type = "Jenis" -version = "Versi" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/panggilan" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Model aktif" -avgLatency = "Latensi rata-rata" -included = "Termasuk" -monthlySpend = "Pengeluaran model bulanan" - -[portal.infrastructure.models.routing] -empty = "Tidak ada aturan routing yang dikonfigurasi." -heading = "Aturan routing" -sub = "Model mana yang menangani setiap operasi. Default berlaku saat tidak ada aturan yang lebih spesifik yang cocok." -subLocked = "Rutekan operasi ke model tertentu — tersedia pada paket berbayar." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Tingkatkan ke Pro untuk mengontrol model mana yang menangani setiap operasi dan jenis dokumen." -title = "Routing model adalah fitur berbayar" - -[portal.infrastructure.models.routingColumns] -default = "Default" -docType = "Jenis dokumen" -modelForAria = "Model untuk {{operation}}" -operation = "Operasi" -routedTo = "Dirutekan ke" - -[portal.infrastructure.modelTypeLabel] -classification = "Klasifikasi" -extraction = "Ekstraksi" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Menurun" -down = "Down" -healthy = "Sehat" - -[portal.infrastructure.security.access.byok] -description = "Sediakan kunci dari KMS Anda sendiri. Stirling mengenkripsi dengannya tetapi masih dapat membaca." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Kunci tidak pernah meninggalkan KMS Anda. Stirling hanya menyimpan ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling mengelola kunci enkripsi. Paling sederhana — tanpa operasi kunci di sisi Anda." -label = "Kunci yang dipegang Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Kebijakan akses dokumen" -subheading = "Mengontrol siapa yang dapat mendekripsi dokumen yang diproses saat disimpan." - -[portal.infrastructure.security.attestations] -heading = "Atestasi kepatuhan" -noReport = "Tidak ada laporan tersedia" -subheading = "Postur audit per framework, dengan laporan tersedia pada kontrol yang diaudit." -viewReport = "Lihat laporan →" - -[portal.infrastructure.security.compliance] -heading = "Kepatuhan" -subheading = "Atestasi dan sertifikasi yang mencakup platform Stirling." - -[portal.infrastructure.security.empty] -description = "Konfigurasi keamanan workspace Anda akan muncul di sini." -title = "Postur keamanan tidak tersedia" - -[portal.infrastructure.security.hyokBanner] -description = "Dengan HYOK, kunci enkripsi tidak pernah meninggalkan KMS Anda. Stirling menyimpan dan memproses hanya ciphertext yang dapat Anda cabut kapan saja." -title = "Stirling tidak dapat mendekripsi dokumen Anda" - -[portal.infrastructure.security.ipAllowlist] -empty = "Tidak ada rentang IP yang dikonfigurasi — semua IP diizinkan." -heading = "IP allowlist" -sub = "Akses API dibatasi ke rentang CIDR ini." -subLocked = "Batasi akses API ke rentang IP yang dikenal — tersedia pada paket berbayar." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Tingkatkan ke Pro untuk membatasi akses API ke jaringan tertentu." -title = "IP allowlisting adalah fitur berbayar" - -[portal.infrastructure.security.ipColumns] -added = "Ditambahkan" -addedBy = "Ditambahkan oleh" -cidr = "CIDR" -label = "Label" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "Manajemen kunci enkripsi" -keyId = "Pengidentifikasi kunci" -lastRotated = "Terakhir dirotasi" -rotateKey = "Rotasi kunci" -rotationPolicy = "Kebijakan rotasi" -subheading = "Penitipan kunci yang mengenkripsi dokumen saat disimpan — siapa yang dapat mendekripsi, dan bagaimana kunci dirotasi." - -[portal.infrastructure.security.managedBanner] -description = "Penitipan bring-your-own-key (BYOK) dan hold-your-own-key (HYOK) tersedia pada Enterprise. Tingkatkan untuk menyediakan kunci dari KMS Anda sendiri." -title = "Kunci dikelola oleh Stirling pada paket Anda" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia Pacific" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · batas data GDPR" -label = "European Union" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "United States" - -[portal.infrastructure.security.residencyHeader] -heading = "Residensi data" -subheading = "Tempat dokumen disimpan dan diproses." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} digunakan" - -[portal.infrastructure.storage.empty] -description = "Penyimpanan yang terhubung dan penggunaan muncul di sini." -title = "Tidak ada penyimpanan yang dikonfigurasi" - -[portal.infrastructure.storage.lifecycle] -active = "Aktif" -activeRange = "0–{{value}}h" -archived = "Diarsipkan" -coldStorage = "cold storage" -deleted = "Dihapus" -never = "tidak pernah" -purged = "dibersihkan" - -[portal.infrastructure.storage.providers] -connect = "Hubungkan" -connected = "Terhubung" -heading = "Penyedia terhubung" -subheading = "Tempat artefak yang diproses ditulis." - -[portal.infrastructure.storage.retention] -heading = "Retensi" -subheading = "Berapa lama artefak disimpan sebelum penghapusan siklus hidup." -windowLabel = "Jendela retensi default" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} hari" -days_other = "{{count}} hari" -never = "Jangan pernah hapus" - -[portal.infrastructure.storage.totalUsage] -heading = "Total penggunaan" -progressLabel = "Penyimpanan digunakan" -subheading = "Penyimpanan yang digunakan di seluruh penyedia terhubung." - [portal.infrastructure.tabs] apiKeys = "API Key" audit = "Log Audit" @@ -7715,11 +7657,9 @@ security = "Keamanan" storage = "Penyimpanan" [portal.integrations] -addAnother = "Tambah lagi" availableHeading = "Tersedia" comingSoonHeading = "Segera hadir" connect = "Hubungkan" -connectedHeading = "Terhubung" connectionCount_one = "{{count}} koneksi" connectionCount_other = "{{count}} koneksi" customApi = "API kustom" @@ -7737,6 +7677,10 @@ security = "Keamanan" signing = "Penandatanganan" storage = "Penyimpanan" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Terhubung" @@ -7767,7 +7711,6 @@ integrations = "Integrasi" pipelines = "Pipeline" policies = "Kebijakan" procurement = "Pengadaan" -settings = "Pengaturan" sources = "Sumber" usage = "Penggunaan & Penagihan" users = "Pengguna" @@ -7784,7 +7727,7 @@ title = "Pipeline" newPipeline = "Pipeline baru" [portal.pipelines.builder] -addStep = "Tambah alat" +activate = "Activate" back = "Kembali ke pipeline" cannotFollow = "Tidak dapat mengambil {{produced}}" chooseAccount = "Pilih akun" @@ -7792,28 +7735,38 @@ chooseDestination = "Pilih tujuan" chooseOperation = "Pilih apa yang dilakukan langkah ini" chooseSource = "Pilih sumber" discard = "Buang perubahan" -enabled = "Diaktifkan" inputs = "Input" inputSource = "Sumber input" inputTrigger = "Pemicu" keepEditing = "Lanjutkan mengedit" +moreActions = "More actions" needsConfiguring = "Perlu disiapkan" -needsUpload = "Memerlukan file yang di-upload" -noSources = "Belum ada sumber yang terhubung. Buat satu untuk digunakan sebagai input." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Tidak ada alat yang cocok dengan pencarian Anda." -pipelineSettings = "Pengaturan pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Cari alat" -selectToolBody = "Tambahkan alat untuk membangun pipeline Anda." -selectToolTitle = "Belum ada alat" sendToSystem = "Kirim ke sistem lain" stepsIncompatible = "Langkah-langkah ini tidak dapat berjalan pada hasil dari langkah sebelumnya: {{tools}}." stepsNeedSetup = "Langkah-langkah ini masih perlu disiapkan sebelum menyimpan: {{tools}}." -toolSettings = "Pengaturan alat" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Operasi tidak dikenali, dipertahankan apa adanya." unsavedBody = "Anda memiliki perubahan yang belum disimpan. Simpan sebelum keluar, atau buang?" unsavedTitle = "Perubahan belum disimpan" -uploadUnsupported = "File yang di-upload belum didukung di pipeline, jadi langkah ini tidak dapat disimpan: {{tools}}." usesDefaults = "Berjalan dengan pengaturan default" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Menggabungkan setiap file dari langkah sebelumnya" @@ -7825,31 +7778,36 @@ undeclared-operation = "Tidak dapat memeriksa apa yang diterima langkah ini" [portal.pipelines.composer] addTool = "Tambah alat" -cancel = "Batal" -chainEmpty = "Tambahkan alat untuk mulai membangun pipeline Anda." create = "Buat pipeline" +createPaused = "Create paused" editingUnsupported = "Menampilkan parameter alat ini untuk diedit belum didukung." -moveDown = "Pindah ke bawah" -moveUp = "Pindah ke atas" +editSource = "Edit source" name = "Nama" namePlaceholder = "mis. Redaction sweep" noToolSettings = "Alat ini tidak memiliki pengaturan yang dapat dikonfigurasi." -operations_one = "Operasi ({{count}})" -operations_other = "Operasi ({{count}})" output = "Output" -removeStep = "Hapus operasi" save = "Simpan perubahan" scheduleEvery = "Jalankan setiap" -sources = "Sumber" -sourcesLoading = "Memuat sumber..." trigger = "Trigger" triggerManual = "Hanya manual" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "hari" hours = "jam" minutes = "menit" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Hapus \"{{name}}\"? Ini tidak dapat dibatalkan." cancel = "Batal" @@ -7867,6 +7825,37 @@ connectSource = "Hubungkan sumber" description = "Buat pipeline pertama Anda: pilih sumber yang dijalankannya, rangkai operasi, dan pilih tempat output dituju." title = "Belum ada pipeline" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktif" paused = "Dijeda" @@ -7879,7 +7868,6 @@ completed_one = "Jalankan selesai." completed_other = "Semua {{count}} proses berjalan selesai." empty = "Tidak ada yang dijalankan: sumber tidak memiliki dokumen untuk diproses." failed = "Proses berjalan gagal: {{error}}" -historyCleared = "Riwayat dihapus. Proses berikutnya memproses ulang semua yang saat ini ada di sumber." inFlight = "Tidak ada yang baru untuk dijalankan: dokumen masih diproses dari proses sebelumnya." parked_one = "Tidak ada yang dijalankan: {{count}} dokumen sebelumnya gagal dan diparkir. Perbaiki penyebabnya, lalu hapus riwayat untuk mencobanya lagi." parked_other = "Tidak ada yang dijalankan: {{count}} dokumen sebelumnya gagal dan diparkir. Perbaiki penyebabnya, lalu hapus riwayat untuk mencobanya lagi." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Sumber" status = "Status" steps = "Langkah" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Pemantauan folder" @@ -8209,6 +8198,24 @@ label = "Lampirkan ke issue Jira" description = "Mengirim dokumen yang diproses sebagai lampiran." label = "Email dokumen (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Menulis dokumen yang diproses ke dalam folder." label = "Upload ke Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Maksimal" subtitle = "Uji coba gratis Anda berjalan hingga {{date}}. Tidak perlu kartu." title = "Uji coba Enterprise" -[portal.search] -ariaLabel = "Cari" -placeholder = "Cari Stirling — endpoint, pipeline, dokumen…" - -[portal.search.empty] -noActionsDescription = "Tindakan cepat akan muncul di sini setelah tersedia." -noActionsTitle = "Tidak ada tindakan cepat" -noMatches = "Tidak ada kecocokan untuk \"{{query}}\"" -noMatchesDescription = "Coba kata kunci lain atau jelajahi katalog." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Batal" confirm = "Hapus" title = "Hapus sumber?" -[portal.sources.empty] -description = "Hubungkan folder (dan, segera, cloud storage) agar kebijakan Anda memiliki tempat untuk menarik dokumen." -title = "Belum ada sumber yang terhubung" - [portal.sources.kpi] inUse = "Digunakan" total = "Koneksi" @@ -8785,6 +8778,7 @@ unused = "Tidak digunakan" documents = "Dokumen" source = "Sumber" status = "Status" +type = "Type" usedBy = "Kebijakan" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Kebijakan & Privasi" [settings.preferences] title = "Preferensi" -[settings.search] -placeholder = "Cari halaman pengaturan..." - [settings.security] description = "Perbarui kata sandi Anda untuk menjaga keamanan akun." title = "Keamanan" @@ -10128,6 +10119,7 @@ last = "Halaman terakhir" maintainRatio = "Alihkan pertahankan rasio aspek" next = "Halaman berikutnya" noSavedSigs = "Tidak ditemukan tanda tangan yang disimpan" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Tanda Tangan Pribadi" previous = "Halaman sebelumnya" redo = "Ulangi" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Penghapusan latar belakang gagal" hint = "Unggah gambar PNG atau JPG dari tanda tangan Anda" label = "Unggah gambar tanda tangan" placeholder = "Pilih berkas gambar" +previewAlt = "Current image signature" processing = "Memproses gambar..." removeBackground = "Hapus latar belakang putih (buat transparan)" @@ -10179,6 +10172,17 @@ saved = "Pilih tanda tangan tersimpan di atas, lalu klik di mana saja pada PDF u text = "Setelah memasukkan nama Anda di atas, klik di mana saja pada PDF untuk menempatkan tanda tangan Anda." title = "Cara menambahkan tanda tangan" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Pindahkan Tanda Tangan" pause = "Jeda penempatan" @@ -10625,6 +10629,23 @@ title = "Unggah ke Server" updateButton = "Perbarui di Server" uploadButton = "Unggah ke Server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Kembali" pdfTools = "Alat PDF" placeholder = "Pilih alat untuk memulai" premiumFeature = "Fitur premium:" -searchTools = "Cari alat" toolsHeader = "Alat" viewAllTools = "Lihat semua alat" @@ -10822,6 +10842,9 @@ removal = "Penghapusan" signing = "Penandatanganan" verification = "Verifikasi" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Tidak ada alat ditemukan" noTools = "Tidak ada alat tersedia" @@ -10979,6 +11002,13 @@ approver = "Menyetujui kebijakan" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Batalkan undangan ke {{email}}? Mereka tidak akan dapat bergabung dengan tautan saat ini." cancelInviteTitle = "Batalkan undangan" @@ -10998,10 +11028,8 @@ title = "Belum ada anggota" addToTeam = "Tambahkan ke tim" guestCount = "{{count}} tamu" guests = "Tamu" -guestsDesc = "Kolaborator eksternal, dibatasi pada yang Anda bagikan. Hanya Editor." ledBy = "dipimpin oleh {{owner}}" org = "Organisasi" -orgDesc = "Pemilik dengan otoritas seluruh org dan persetujuan kebijakan" owners = "{{count}} pemilik" team = "Tim {{name}}" teamMeta = "{{count}} orang" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Diundang oleh {{who}}" cancel = "Batal" -count = "{{count}} tertunda" -desc = "Orang yang diundang yang belum bergabung. Mereka menempati satu kursi hingga menerima." expiresInDays_one = "Kedaluwarsa dalam {{count}} hari" expiresInDays_other = "Kedaluwarsa dalam {{count}} hari" expiresToday = "Kedaluwarsa hari ini" title = "Undangan tertunda" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Terjadi kesalahan saat menghubungi backend, atau Anda tidak memiliki akses. Coba lagi." title = "Tidak dapat memuat anggota" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Cari lampiran" title = "Lampiran" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Judul bookmark" bookmarkTitleRequired = "Judul bookmark wajib diisi" closeSidebar = "Tutup bilah sisi bookmark" collapseAll = "Ciutkan semua bookmark" +empty = "No bookmarks in this document" expandAll = "Luaskan semua bookmark" searchPlaceholder = "Cari bookmark" +title = "Bookmarks" [viewer.comments] addComment = "Tambahkan komentar" @@ -11264,10 +11297,12 @@ locateAnnotation = "Temukan di dokumen" moreActions = "Tindakan lainnya" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Halaman {{page}}" placingHint = "Klik sebuah halaman untuk menempatkan… (batalkan)" removeCommentOnly = "Hanya hapus komentar" saveReply = "Simpan balasan" +searchPlaceholder = "Search comments" title = "Komentar" typeComment = "Komentar" typeInsertText = "Sisipkan Teks" @@ -11290,6 +11325,7 @@ unsavedDesc = "Anda memiliki perubahan yang belum disimpan" closeSidebar = "Tutup bilah sisi layer" hideAll = "Sembunyikan semua layer" showAll = "Tampilkan semua layer" +title = "Layers" [viewer.link] delete = "Hapus tautan" @@ -11646,6 +11682,7 @@ exitRedaction = "Keluar dari Mode Redaksi" exportAll = "Ekspor PDF" exportSelected = "Ekspor Halaman yang Dipilih" formFill = "Isi Formulir" +hideToolbar = "Hide toolbar" multiTool = "Multi-Tool" panMode = "Mode Geser" print = "Cetak PDF" @@ -11666,6 +11703,7 @@ selectAll = "Pilih Semua" selectByNumber = "Pilih berdasarkan Nomor Halaman" selectLanguage = "Pilih bahasa" share = "Bagikan" +showToolbar = "Show toolbar" toggleAnnotations = "Tampilkan/Sembunyikan Anotasi" toggleAttachments = "Tampilkan/Sembunyikan Lampiran" toggleBookmarks = "Tampilkan/Sembunyikan Penanda Halaman" diff --git a/frontend/editor/public/locales/it-IT/translation.toml b/frontend/editor/public/locales/it-IT/translation.toml index f47347b349..53bfd1fd46 100644 --- a/frontend/editor/public/locales/it-IT/translation.toml +++ b/frontend/editor/public/locales/it-IT/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabeto" apply = "Applica" applyAndContinue = "Applica e continua" -areYouSure = "Sei sicuro di voler uscire?" back = "Indietro" black = "Nero" blue = "Blu" @@ -106,6 +105,7 @@ undoSuccess = "Operazione annullata con successo" unknown = "Sconosciuto" unpin = "Sblocca file (sostituisci dopo l'esecuzione dello strumento)" unsavedChanges = "Hai modifiche non salvate al tuo PDF. Cosa vuoi fare?" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Modifiche non salvate" unsupported = "Non supportato" username = "Nome utente" @@ -1879,6 +1879,9 @@ width = "Spessore" [app] description = "L’alternativa gratuita ad Adobe Acrobat (10M+ download)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Converti in PDF/A-3b" convertToPdfA3bDescription = "Crea un PDF per archiviazione con allegati incorporati" @@ -2933,12 +2936,16 @@ text = "Per rendere questi permessi immodificabili, usa lo strumento Aggiungi pa [chat.actions] copy = "Copia messaggio" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Chiudi chat" open = "Apri Stirling assistente IA" [chat.header] agentMenu = "Opzioni agente Stirling" +assistant = "AI Assistant" clearChat = "Cancella chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Questa operazione utilizzerà i tuoi crediti cloud" tooltip = "Preleva il colore dallo schermo" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Scegli il colore" [common] +actions = "Actions" back = "Indietro" cancel = "Annulla" close = "Chiudi" +codeSample = "Code sample" collapse = "Comprimi" confirm = "Conferma" continue = "Continua" copied = "Copiato!" copy = "Copia" done = "Fatto" +enterValue = "Enter value" error = "Errore" expand = "Espandi" loading = "Caricamento..." next = "Successivo" +open = "Open" preview = "Anteprima" previous = "Precedente" refresh = "Aggiorna" remaining = "Rimanenti" retry = "Riprova" save = "Salva" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Cancella selezionati" @@ -3185,6 +3199,7 @@ title = "Metodo di compressione" [compress.settings] desiredSize = "Dimensione file desiderata" desiredSizePlaceholder = "Inserisci dimensione" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "La compressione è un modo semplice per ridurre la dimensione del file. Scegli Dimensione File per inserire una dimensione obiettivo e lascia che regoliamo la qualità per te. Scegli Qualità per impostare manualmente l’intensità della compressione." @@ -3387,6 +3402,24 @@ pdfOptions = "Opzioni PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Seleziona prima un formato sorgente" settings = "Impostazioni" single = "Singolo" @@ -3565,7 +3598,11 @@ tags = "automazione,cartella,scansione,cartella di controllo,cartella di tendenz tags = "SSO,single sign-on,autenticazione,SAML,OAuth,OIDC,login,enterprise,provider di identità,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Cerca..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Modifica" @@ -3824,11 +3861,13 @@ shareSelected = "Condividi selezionati" sharing = "Condivisione" showAll = "Mostra tutto" showHistory = "Mostra cronologia" +sortBy = "Sort files" sortByDate = "Ordina per data" sortByName = "Ordina per nome" sortBySize = "Ordina per dimensione" storage = "Archiviazione" storageState = "Archiviazione" +storageUsed = "Storage used" synced = "Sincronizzato" title = "Carica file PDF" toolChain = "Strumenti applicati" @@ -3874,8 +3913,12 @@ addFiles = "Aggiungi file" addingFiles = "Aggiunta file…" collapse = "Comprimi barra laterale" customizeGroups = "Personalizza gruppi" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Apri file per iniziare" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Espandi barra laterale" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive non è configurato" @@ -3888,15 +3931,17 @@ openFromComputer = "Apri dal computer" openSettings = "Apri impostazioni" other = "Altro" recent = "Recenti" -search = "Cerca" -searchPlaceholder = "Cerca file..." viewAll = "Visualizza tutti i {{count}} file" [fileSidebar.fileItem] closeViewer = "Chiudi visualizzatore" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Elimina" +duplicate = "Duplicate" moreActions = "Altre azioni" openInViewer = "Apri nel visualizzatore" +rename = "Rename" savedToServer = "Salvato sul server" updateOnServer = "Aggiorna sul server" uploadToServer = "Carica sul server" @@ -3908,6 +3953,14 @@ reset = "Ripristina impostazioni predefinite" subtitle = "Raggruppa i tuoi file in categorie principali. Aggiungi etichette esistenti o nuove a una categoria, rinominala o creane una tua. I file che non rientrano in nessuna delle tue categorie vengono visualizzati in “Altro”." title = "Categorie della barra laterale" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Aggiungi allo spazio di lavoro" addToWorkspaceCount = "Aggiungi {{count}} allo spazio di lavoro" @@ -3918,7 +3971,6 @@ backToMyFiles = "Torna ai miei file" breadcrumbs = "Percorso cartella" cancel = "Annulla" classification = "Classificazione" -clearSearch = "Cancella ricerca" clearSelection = "Cancella selezione" closeDetails = "Chiudi dettagli" create = "Crea" @@ -3950,6 +4002,7 @@ downloadAll = "Scarica tutto" downloadVersion = "Scarica questa versione" dropOverlay = "Rilascia i file per caricarli" dropOverlaySub = "I file iniziano in Locale. Usa 'Sposta in' o 'Salva nel cloud' per organizzarli in una cartella." +duplicate = "Duplicate" file = "File" fileInfo = "File info" fileMenu = "Azioni file" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Ridimensiona albero cartelle (tasti freccia, Shift per passi save = "Salva" saveToServer = "Salva sul server" saveToServerDisabledHint = "Il salvataggio sul server non è abilitato su questo server. Chiedi al tuo amministratore di abilitarlo." -search = "Cerca" -searchPlaceholder = "Cerca in questa cartella e sottocartelle" selectAll = "Seleziona tutto" selectAllHint = "Fai clic per selezionare tutto. Suggerimento: tieni premuto Ctrl (o Cmd) per aggiungere i file uno alla volta, Shift per selezionare un intervallo." selectedCount = "{{count}} selezionati" @@ -4044,6 +4095,10 @@ title = "Nessun file cloud ancora" hint = "I file salvati senza caricamento restano qui. Rilascia un file per aggiungerne uno." title = "Nessun file solo locale" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "I file che apri o modifichi appariranno qui." title = "Ancora nulla di modificato" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Impossibile eliminare la cartella." deleteFolderFailedDetail = "Impossibile eliminare la cartella: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Impossibile aggiornare l'aspetto della cartella." folderAppearanceFailedDetail = "Impossibile aggiornare l'aspetto della cartella: {{message}}" moveFilesFailed = "Impossibile spostare i file." @@ -4113,6 +4170,11 @@ localHint = "Archiviato solo in questo browser" shared = "Condivisi" sharedHint = "Condivisi con te tramite link" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Più vecchi prima" modifiedDesc = "Più recenti prima" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Nessun file disponibile nell'archivio. Apri prima alcuni open = "Apri" openFile = "Apri file" openFiles = "Apri file" +selectFile = "Select {{name}}" selectFromStorage = "Seleziona dall’archiviazione" upload = "Carica" uploadFile = "Carica file" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Tutto salvato" +analyzingFields = "Analysing form fields..." extractCsvError = "Impossibile estrarre CSV" extractXlsxError = "Impossibile estrarre XLSX" +filled = "filled" flattenAfterFilling = "Appiattisci dopo la compilazione" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obb" requiredFieldsError = "Compila tutti i campi obbligatori" rescanFields = "Riscansiona campi" @@ -4795,9 +4863,6 @@ title = "Immagine a PDF" [imageToPdf] tags = "conversione,img,jpg,immagine,foto" -[infoBanner] -dismiss = "Ignora" - [invite] acceptError = "Impossibile creare l'account" accountFor = "Creazione account per" @@ -4979,6 +5044,34 @@ uploadSuccess = "Caricamento riuscito!" uploadSuccessMessage = "Le tue immagini sono state trasferite." validating = "Validazione della sessione..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Scansiona per caricare foto. Le immagini vengono convertite automaticamente in PDF." descriptionNoConvert = "Scansiona per caricare foto dal tuo dispositivo mobile." @@ -4997,8 +5090,13 @@ title = "Carica da dispositivo mobile" tags = "Strumento multiplo,operazione multipla,interfaccia utente,trascinamento clic,front-end,lato client" title = "Multifunzione PDF" -[navbar] -search = "Cerca" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "L'autenticazione non è andata a buon fine. Puoi chiudere questa finestra e riprovare." @@ -5127,6 +5225,7 @@ activeFiles = "La vista File attivi mostra tutti i PDF caricati allTools = "Questo è il pannello Tutti gli Strumenti, dove puoi sfogliare e selezionare tutti gli strumenti PDF disponibili." close = "Chiudi" cropSettings = "Ora che abbiamo selezionato il file che vogliamo ritagliare, possiamo configurare lo strumento Ritaglia per scegliere l'area a cui ritagliare il PDF." +dialogLabel = "Onboarding" fileCheckbox = "Facendo clic su uno dei file lo selezioni per l'elaborazione. Puoi selezionare più file per operazioni in blocco." fileReplacement = "Il file modificato sostituirà automaticamente l'originale nel Banco di lavoro, consentendoti di farlo passare facilmente attraverso altri strumenti." filesButton = "Il pulsante File sulla barra di accesso rapido consente di caricare PDF su cui usare gli strumenti." @@ -5563,6 +5662,7 @@ freeBody = "Visualizza, modifica, unisci, dividi, firma, inserisci filigrana, co freeTitle = "Modifica PDF illimitata" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} PDF gratuiti" metaCategories = "Automazione · IA · Richieste API" @@ -5642,6 +5742,7 @@ automation = "le automazioni" default = "questa funzionalità" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ limite di {{amount}}" metaCategories = "Automazione · IA · Spesa API" resets = "Si azzera a ogni periodo di fatturazione" @@ -5970,6 +6071,11 @@ header = "Da PDF a PDF/A" tags = "archivio,a lungo termine,standard,conversione,archiviazione,conservazione" title = "Da PDF a PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "stampa,standard,conversione,produzione,prestampa,archiviazione" title = "PDF in PDF/X" @@ -6045,6 +6151,7 @@ small = "500 Crediti" xsmall = "100 Crediti" [plan.availablePlans] +currency = "Billing currency" subtitle = "Scegli il piano adatto alle tue esigenze" title = "Piani disponibili" @@ -6624,6 +6731,7 @@ reachedTitle = "Limite di spesa mensile raggiunto" title = "Impossibile aprire il portale Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "di {{allowance}} PDF gratuiti usati" capSuffix_other = "di {{allowance}} PDF gratuiti usati" eyebrow = "Prova Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Invia un documento o una notifica via email quando una policy viene eseguita." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Archivia documenti elaborati in Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDK ufficiali" beta = "Beta" deprecated = "Deprecato" -[portal.docs.search] -empty = "Nessun docs corrispondente" -placeholder = "Cerca docs" -results = "{{count}} risultati" - [portal.docs.skills] eyebrow = "SKILL" lead = "Capacità incluse e denominate che il tuo agente invoca come singolo strumento. Ogni skill è una catena di operazioni deterministica con valutazioni associate." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Nessun documento corrisponde a questo filtro." rowActions = "Azioni riga" sensitiveLabel = "Sensibile" -sensitiveTitle = "Sensibile: accesso richiesto" [portal.documents.table.columns] action = "Pipeline / Azione" document = "Documento" +labels = "Labels" product = "Prodotto" status = "Stato" time = "Ora" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Ultima visualizzazione" region = "Regione" status = "Stato" +target = "Target" version = "Versione" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Questa vista ha riscontrato un errore imprevisto. Riprova oppure retry = "Riprova" title = "Qualcosa è andato storto in questa pagina" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Indietro" body = "Scarica l'app desktop o ospita autonomamente il server." @@ -7380,11 +7560,6 @@ cancel = "Annulla" confirm = "Revoca chiave" title = "Revoca chiave API" -[portal.infrastructure.attestationLabel] -attested = "Attestato" -inScope = "In ambito" -notApplicable = "N/D" - [portal.infrastructure.audit] filterAriaLabel = "Filtra gli eventi di audit per categoria" heading = "Log di audit" @@ -7456,11 +7631,6 @@ info = "Info" success = "Successo" warning = "Avviso" -[portal.infrastructure.certLabel] -certified = "Certificato" -inProgress = "In corso" -notStarted = "Non avviato" - [portal.infrastructure.createKey] cancel = "Annulla" createKey = "Crea chiave" @@ -7474,238 +7644,10 @@ subtitleCreated = "Copia ora questo segreto: non verrà mostrato di nuovo." title = "Crea chiave API" titleCreated = "Chiave creata" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "In coda" -rolledBack = "Ripristinato" -rolling = "Distribuzione in corso" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Distribuito da" -environment = "Ambiente" -product = "Prodotto" -status = "Stato" -version = "Versione" -when = "Quando" - -[portal.infrastructure.deployments.recent] -heading = "Distribuzioni recenti" -subheading = "Gli ultimi rollout tra prodotti e ambienti." - -[portal.infrastructure.deployments.regionColumns] -instances = "Istanze" -latency = "Latenza" -load = "Carico" -p99 = "P99" -region = "Regione" -status = "Stato" -throughput = "Throughput" -uptime = "Uptime" -version = "Versione" - -[portal.infrastructure.deployments.regions] -heading = "Regioni" -subheading = "Integrità in tempo reale per ogni regione Stirling distribuita: latenza, carico e versione di rollout." - -[portal.infrastructure.deployments.regions.empty] -description = "Le regioni distribuite appariranno qui una volta eseguito il provisioning del tuo workspace." -title = "Nessuna regione distribuita" - [portal.infrastructure.keyLabel] active = "Attiva" revoked = "Revocata" -[portal.infrastructure.modelLabel] -active = "Attivo" -degraded = "Degradato" -disabled = "Disabilitato" - -[portal.infrastructure.models] -heading = "Modelli" -msValue = "{{value}} ms" -subheading = "Il catalogo dei modelli e il routing che alimentano l'elaborazione dei documenti nel tuo workspace." - -[portal.infrastructure.models.byom] -description = "Registra un modello on-prem o self-hosted e fissalo a una regione per l'elaborazione vincolata alla residenza dei dati." -title = "Porta il tuo modello" - -[portal.infrastructure.models.catalogue] -heading = "Catalogo" -sub = "Modelli gestiti disponibili per il tuo workspace, con latenza e costi in tempo reale." -subEnterprise = "Modelli gestiti, bring-your-own e on-prem, con pinning per regione disponibile." - -[portal.infrastructure.models.catalogue.empty] -description = "I modelli nel catalogo del tuo workspace appariranno qui." -title = "Nessun modello disponibile" - -[portal.infrastructure.models.columns] -cost = "Costo" -latency = "Latenza" -load = "Carico" -model = "Modello" -status = "Stato" -type = "Tipo" -version = "Versione" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/chiamata" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Modelli attivi" -avgLatency = "Latenza media" -included = "Incluso" -monthlySpend = "Spesa mensile modelli" - -[portal.infrastructure.models.routing] -empty = "Nessuna regola di routing configurata." -heading = "Regole di routing" -sub = "Quale modello gestisce ogni operazione. Il predefinito si applica quando nessuna regola più specifica corrisponde." -subLocked = "Instrada le operazioni a modelli specifici: disponibile nei piani a pagamento." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Passa a Pro per controllare quale modello gestisce ogni operazione e tipo di documento." -title = "Il routing dei modelli è una funzionalità a pagamento" - -[portal.infrastructure.models.routingColumns] -default = "Predefinito" -docType = "Tipo di documento" -modelForAria = "Modello per {{operation}}" -operation = "Operazione" -routedTo = "Instradato a" - -[portal.infrastructure.modelTypeLabel] -classification = "Classificazione" -extraction = "Estrazione" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degradato" -down = "Non disponibile" -healthy = "Integro" - -[portal.infrastructure.security.access.byok] -description = "Fornisci una chiave dal tuo KMS. Stirling cifra con essa ma può comunque leggere." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Le chiavi non lasciano mai il tuo KMS. Stirling conserva solo ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling gestisce le chiavi di crittografia. Il più semplice: zero operazioni sulle chiavi da parte tua." -label = "Chiavi detenute da Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Criterio di accesso ai documenti" -subheading = "Controlla chi può decifrare i documenti elaborati a riposo." - -[portal.infrastructure.security.attestations] -heading = "Attestazioni di conformità" -noReport = "Nessun report disponibile" -subheading = "Postura di audit framework per framework, con report disponibili sui controlli attestati." -viewReport = "Visualizza report →" - -[portal.infrastructure.security.compliance] -heading = "Conformità" -subheading = "Attestazioni e certificazioni che coprono la piattaforma Stirling." - -[portal.infrastructure.security.empty] -description = "La configurazione di sicurezza del tuo workspace apparirà qui." -title = "Postura di sicurezza non disponibile" - -[portal.infrastructure.security.hyokBanner] -description = "Con HYOK, le chiavi di crittografia non lasciano mai il tuo KMS. Stirling archivia ed elabora solo ciphertext che puoi revocare in qualsiasi momento." -title = "Stirling non può decifrare i tuoi documenti" - -[portal.infrastructure.security.ipAllowlist] -empty = "Nessun intervallo IP configurato: tutti gli IP sono consentiti." -heading = "Allowlist IP" -sub = "L'accesso API è limitato a questi intervalli CIDR." -subLocked = "Limita l'accesso API a intervalli IP noti: disponibile nei piani a pagamento." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Passa a Pro per limitare l'accesso API a reti specifiche." -title = "L'allowlist IP è una funzionalità a pagamento" - -[portal.infrastructure.security.ipColumns] -added = "Aggiunto" -addedBy = "Aggiunto da" -cidr = "CIDR" -label = "Etichetta" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritmo" -heading = "Gestione delle chiavi di crittografia" -keyId = "Identificatore chiave" -lastRotated = "Ultima rotazione" -rotateKey = "Ruota chiave" -rotationPolicy = "Criterio di rotazione" -subheading = "Custodia delle chiavi che cifrano i documenti a riposo: chi può decifrare e come ruotano le chiavi." - -[portal.infrastructure.security.managedBanner] -description = "La custodia bring-your-own-key (BYOK) e hold-your-own-key (HYOK) è disponibile su Enterprise. Aggiorna per fornire chiavi dal tuo KMS." -title = "Le chiavi sono gestite da Stirling nel tuo piano" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia Pacifico" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · limite dati GDPR" -label = "Unione Europea" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Stati Uniti" - -[portal.infrastructure.security.residencyHeader] -heading = "Residenza dei dati" -subheading = "Dove i documenti vengono archiviati ed elaborati." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} usato" - -[portal.infrastructure.storage.empty] -description = "Lo storage connesso e l'utilizzo appariranno qui." -title = "Nessuno storage configurato" - -[portal.infrastructure.storage.lifecycle] -active = "Attivo" -activeRange = "0–{{value}}d" -archived = "Archiviato" -coldStorage = "cold storage" -deleted = "Eliminato" -never = "mai" -purged = "eliminato definitivamente" - -[portal.infrastructure.storage.providers] -connect = "Connetti" -connected = "Connesso" -heading = "Provider connessi" -subheading = "Dove vengono scritti gli artefatti elaborati." - -[portal.infrastructure.storage.retention] -heading = "Conservazione" -subheading = "Per quanto tempo gli artefatti vengono conservati prima dell'eliminazione del ciclo di vita." -windowLabel = "Finestra di conservazione predefinita" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} giorno" -days_other = "{{count}} giorni" -never = "Non eliminare mai" - -[portal.infrastructure.storage.totalUsage] -heading = "Utilizzo totale" -progressLabel = "Storage utilizzato" -subheading = "Storage consumato su tutti i provider connessi." - [portal.infrastructure.tabs] apiKeys = "Chiavi API" audit = "Log di audit" @@ -7715,11 +7657,9 @@ security = "Sicurezza" storage = "Storage" [portal.integrations] -addAnother = "Aggiungi un'altra" availableHeading = "Disponibili" comingSoonHeading = "In arrivo" connect = "Connetti" -connectedHeading = "Connesse" connectionCount_one = "{{count}} connessione" connectionCount_other = "{{count}} connessioni" customApi = "API personalizzata" @@ -7737,6 +7677,10 @@ security = "Sicurezza" signing = "Firma" storage = "Archiviazione" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Connessa" @@ -7767,7 +7711,6 @@ integrations = "Integrazioni" pipelines = "Pipeline" policies = "Criteri" procurement = "Approvvigionamento" -settings = "Impostazioni" sources = "Origini" usage = "Utilizzo e fatturazione" users = "Utenti" @@ -7784,7 +7727,7 @@ title = "Pipeline" newPipeline = "Nuova pipeline" [portal.pipelines.builder] -addStep = "Aggiungi strumento" +activate = "Activate" back = "Torna alle pipeline" cannotFollow = "Impossibile accettare {{produced}}" chooseAccount = "Scegli un account" @@ -7792,28 +7735,38 @@ chooseDestination = "Scegli una destinazione" chooseOperation = "Scegli cosa fa questo passaggio" chooseSource = "Scegli un'origine" discard = "Scarta modifiche" -enabled = "Abilitata" inputs = "Input" inputSource = "Origine input" inputTrigger = "Trigger" keepEditing = "Continua a modificare" +moreActions = "More actions" needsConfiguring = "Richiede configurazione" -needsUpload = "Richiede un file caricato" -noSources = "Nessuna origine ancora connessa. Creane una per usarla come input." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Nessuno strumento corrisponde alla tua ricerca." -pipelineSettings = "Impostazioni pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Cerca strumenti" -selectToolBody = "Aggiungi uno strumento per creare la tua pipeline." -selectToolTitle = "Ancora nessuno strumento" sendToSystem = "Invia a un altro sistema" stepsIncompatible = "Questi passaggi non possono essere eseguiti su ciò che produce il passaggio precedente: {{tools}}." stepsNeedSetup = "Questi passaggi devono ancora essere configurati prima del salvataggio: {{tools}}." -toolSettings = "Impostazioni strumento" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Operazione non riconosciuta, mantenuta così com'è." unsavedBody = "Hai modifiche non salvate. Salvale prima di uscire o scartarle?" unsavedTitle = "Modifiche non salvate" -uploadUnsupported = "I file caricati non sono ancora supportati nelle pipeline, quindi questi passaggi non possono essere salvati: {{tools}}." usesDefaults = "Esegue con impostazioni predefinite" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combina ogni file del passaggio precedente" @@ -7825,31 +7778,36 @@ undeclared-operation = "Impossibile verificare cosa accetta questo passaggio" [portal.pipelines.composer] addTool = "Aggiungi strumento" -cancel = "Annulla" -chainEmpty = "Aggiungi uno strumento per iniziare a creare la tua pipeline." create = "Crea pipeline" +createPaused = "Create paused" editingUnsupported = "La visualizzazione di questi parametri dello strumento per la modifica non è ancora supportata." -moveDown = "Sposta giù" -moveUp = "Sposta su" +editSource = "Edit source" name = "Nome" namePlaceholder = "es. Scansione redazione" noToolSettings = "Questo strumento non ha impostazioni configurabili." -operations_one = "Operazione ({{count}})" -operations_other = "Operazioni ({{count}})" output = "Output" -removeStep = "Rimuovi operazione" save = "Salva modifiche" scheduleEvery = "Esegui ogni" -sources = "Origini" -sourcesLoading = "Caricamento origini..." trigger = "Trigger" triggerManual = "Solo manuale" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "giorni" hours = "ore" minutes = "minuti" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Eliminare \"{{name}}\"? Questa azione non può essere annullata." cancel = "Annulla" @@ -7867,6 +7825,37 @@ connectSource = "Connetti un'origine" description = "Crea la tua prima pipeline: scegli le origini su cui viene eseguita, concatena le operazioni e scegli dove inviare l'output." title = "Ancora nessuna pipeline" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Attive" paused = "In pausa" @@ -7879,7 +7868,6 @@ completed_one = "Esecuzione completata." completed_other = "Tutte le {{count}} esecuzioni completate." empty = "Niente da eseguire: le origini non avevano documenti da elaborare." failed = "Esecuzione non riuscita: {{error}}" -historyCleared = "Cronologia cancellata. La prossima esecuzione rielabora tutto ciò che è attualmente nelle origini." inFlight = "Niente di nuovo da eseguire: i documenti sono ancora in elaborazione da un'esecuzione precedente." parked_one = "Niente da eseguire: {{count}} documento non è riuscito in precedenza ed è parcheggiato. Correggi la causa, poi cancella la cronologia per riprovarlo." parked_other = "Niente da eseguire: {{count}} documenti non sono riusciti in precedenza e sono parcheggiati. Correggi la causa, poi cancella la cronologia per riprovarli." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Origini" status = "Stato" steps = "Passaggi" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Monitoraggio cartella" @@ -8209,6 +8198,24 @@ label = "Allega a una issue Jira" description = "Invia il documento elaborato come allegato." label = "Invia il documento via Email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Scrive il documento elaborato in una cartella." label = "Carica su Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Limite raggiunto" subtitle = "La tua trial gratuita dura fino al {{date}}. Nessuna carta richiesta." title = "Trial Enterprise" -[portal.search] -ariaLabel = "Cerca" -placeholder = "Cerca in Stirling — endpoint, pipeline, doc…" - -[portal.search.empty] -noActionsDescription = "Le azioni rapide appariranno qui quando saranno disponibili." -noActionsTitle = "Nessuna azione rapida" -noMatches = "Nessuna corrispondenza per \"{{query}}\"" -noMatchesDescription = "Prova una parola chiave diversa o sfoglia il catalogo." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Annulla" confirm = "Elimina" title = "Eliminare origine?" -[portal.sources.empty] -description = "Collega una cartella (e, presto, cloud storage) così i tuoi criteri avranno un posto da cui prelevare documenti." -title = "Nessuna origine collegata ancora" - [portal.sources.kpi] inUse = "In uso" total = "Connessioni" @@ -8785,6 +8778,7 @@ unused = "Non usata" documents = "Documenti" source = "Origine" status = "Stato" +type = "Type" usedBy = "Criteri" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Policy e privacy" [settings.preferences] title = "Preferenze" -[settings.search] -placeholder = "Cerca nelle pagine delle impostazioni..." - [settings.security] description = "Aggiorna la password per mantenere sicuro il tuo account." title = "Sicurezza" @@ -10128,6 +10119,7 @@ last = "Ultima pagina" maintainRatio = "Attiva il mantenimento delle proporzioni" next = "Prossima pagina" noSavedSigs = "Nessuna firma salvata trovata" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Firme personali" previous = "Pagina precedente" redo = "Rifare" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Rimozione dello sfondo non riuscita" hint = "Carica un'immagine PNG o JPG della tua firma" label = "Carica immagine firma" placeholder = "Seleziona file immagine" +previewAlt = "Current image signature" processing = "Elaborazione immagine..." removeBackground = "Rimuovi sfondo bianco (rendi trasparente)" @@ -10179,6 +10172,17 @@ saved = "Seleziona una firma salvata sopra, poi clicca ovunque sul PDF per posiz text = "Dopo aver inserito il tuo nome sopra, clicca in un punto del PDF per inserire la tua firma." title = "Come aggiungere la firma" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Sposta firma" pause = "Metti in pausa posizionamento" @@ -10625,6 +10629,23 @@ title = "Carica sul server" updateButton = "Aggiorna sul server" uploadButton = "Carica sul server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Sondaggio su Stirling-PDF" @@ -10774,7 +10795,6 @@ goBack = "Torna indietro" pdfTools = "Strumenti PDF" placeholder = "Scegli uno strumento per iniziare" premiumFeature = "Funzione Premium:" -searchTools = "Cerca strumenti" toolsHeader = "Strumenti" viewAllTools = "Visualizza tutti gli strumenti" @@ -10822,6 +10842,9 @@ removal = "Rimozione" signing = "Firma" verification = "Verifica" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nessuno strumento trovato" noTools = "Nessuno strumento disponibile" @@ -10979,6 +11002,13 @@ approver = "Approva criterio" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Annullare l'invito a {{email}}? Non potrà unirsi con il link attuale." cancelInviteTitle = "Annulla invito" @@ -10998,10 +11028,8 @@ title = "Nessun membro ancora" addToTeam = "Aggiungi al team" guestCount = "{{count}} ospiti" guests = "Ospite" -guestsDesc = "Collaboratori esterni, limitati a ciò che hai condiviso. Solo Editor." ledBy = "guidato da {{owner}}" org = "Organizzazione" -orgDesc = "Proprietari con autorità su tutta l'organizzazione e approvazione dei criteri" owners = "{{count}} proprietario" team = "Team {{name}}" teamMeta = "{{count}} persone" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Invitato da {{who}}" cancel = "Annulla" -count = "{{count}} in sospeso" -desc = "Persone invitate che non si sono ancora unite. Occupano un posto finché non accettano." expiresInDays_one = "Scade tra {{count}} giorno" expiresInDays_other = "Scade tra {{count}} giorni" expiresToday = "Scade oggi" title = "Inviti in sospeso" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Qualcosa è andato storto nel contattare il backend, oppure non hai accesso. Riprova." title = "Impossibile caricare i membri" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Cerca allegati" title = "Allegati" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Titolo del segnalibro" bookmarkTitleRequired = "Il titolo del segnalibro è obbligatorio" closeSidebar = "Chiudi la barra laterale dei segnalibri" collapseAll = "Comprimi tutti i segnalibri" +empty = "No bookmarks in this document" expandAll = "Espandi tutti i segnalibri" searchPlaceholder = "Cerca segnalibri" +title = "Bookmarks" [viewer.comments] addComment = "Aggiungi commento" @@ -11264,10 +11297,12 @@ locateAnnotation = "Individua nel documento" moreActions = "Altre azioni" nComments_one = "{{count}} commento" nComments_other = "{{count}} commenti" +noMatch = "No comments match your search" pageLabel = "Pagina {{page}}" placingHint = "Fai clic su una pagina per posizionare… (annulla)" removeCommentOnly = "Rimuovi solo il commento" saveReply = "Salva risposta" +searchPlaceholder = "Search comments" title = "Commenti" typeComment = "Commento" typeInsertText = "Inserisci testo" @@ -11290,6 +11325,7 @@ unsavedDesc = "Hai modifiche non salvate" closeSidebar = "Chiudi la barra laterale dei livelli" hideAll = "Nascondi tutti i livelli" showAll = "Mostra tutti i livelli" +title = "Layers" [viewer.link] delete = "Elimina link" @@ -11646,6 +11682,7 @@ exitRedaction = "Esci dalla modalità di oscuramento" exportAll = "Esporta PDF" exportSelected = "Esporta pagine selezionate" formFill = "Compila modulo" +hideToolbar = "Hide toolbar" multiTool = "Strumento multiplo" panMode = "Modalità mano" print = "Stampa PDF" @@ -11666,6 +11703,7 @@ selectAll = "Seleziona tutto" selectByNumber = "Seleziona per numeri di pagina" selectLanguage = "Seleziona lingua" share = "Condividi" +showToolbar = "Show toolbar" toggleAnnotations = "Mostra/Nascondi annotazioni" toggleAttachments = "Mostra/Nascondi allegati" toggleBookmarks = "Mostra/Nascondi segnalibri" diff --git a/frontend/editor/public/locales/ja-JP/translation.toml b/frontend/editor/public/locales/ja-JP/translation.toml index 292d39f7e0..2d67a7427a 100644 --- a/frontend/editor/public/locales/ja-JP/translation.toml +++ b/frontend/editor/public/locales/ja-JP/translation.toml @@ -1,7 +1,6 @@ alphabet = "アルファベット" apply = "適用" applyAndContinue = "適用して続行" -areYouSure = "本当に離れますか?" back = "戻る" black = "黒" blue = "青" @@ -106,6 +105,7 @@ undoSuccess = "操作を元に戻しました" unknown = "不明" unpin = "ピン留めを解除(ツール実行後に置き換え)" unsavedChanges = "PDF に未保存の変更があります。" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "未保存の変更" unsupported = "未対応" username = "ユーザー名" @@ -1879,6 +1879,9 @@ width = "幅" [app] description = "無料の Adobe Acrobat 代替(1,000万+ ダウンロード)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "PDF/A-3b に変換" convertToPdfA3bDescription = "添付ファイルを埋め込んだアーカイブ用 PDF を作成します" @@ -2933,12 +2936,16 @@ text = "これらの権限を変更不可にするには、パスワード追加 [chat.actions] copy = "メッセージをコピー" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling エージェントのオプション" +assistant = "AI Assistant" clearChat = "チャットをクリア" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "この操作はクラウドクレジットを消費します" tooltip = "画面から色を取得" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "色を選択" [common] +actions = "Actions" back = "戻る" cancel = "キャンセル" close = "閉じる" +codeSample = "Code sample" collapse = "折りたたむ" confirm = "確認" continue = "続行" copied = "コピーしました!" copy = "コピー" done = "完了" +enterValue = "Enter value" error = "エラー" expand = "展開" loading = "読み込み中..." next = "次へ" +open = "Open" preview = "プレビュー" previous = "前へ" refresh = "更新" remaining = "残り" retry = "再試行" save = "保存" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "選択をクリア" @@ -3185,6 +3199,7 @@ title = "圧縮方式" [compress.settings] desiredSize = "目標ファイルサイズ" desiredSizePlaceholder = "サイズを入力" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "圧縮はファイルサイズを簡単に減らす方法です。「ファイルサイズ」を選ぶと目標サイズを入力でき、品質は自動調整します。「品質」を選ぶと圧縮強度を手動設定できます。" @@ -3387,6 +3402,24 @@ pdfOptions = "PDF オプション" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "先に変換元形式を選択してください" settings = "設定" single = "単一" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "検索..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "編集" @@ -3824,11 +3861,13 @@ shareSelected = "選択項目を共有" sharing = "共有" showAll = "すべて表示" showHistory = "履歴を表示" +sortBy = "Sort files" sortByDate = "日付で並べ替え" sortByName = "名前で並べ替え" sortBySize = "サイズで並べ替え" storage = "ストレージ" storageState = "ストレージ" +storageUsed = "Storage used" synced = "同期済み" title = "PDF ファイルをアップロード" toolChain = "適用ツール" @@ -3874,8 +3913,12 @@ addFiles = "ファイルを追加" addingFiles = "ファイルを追加中…" collapse = "サイドバーを折りたたむ" customizeGroups = "グループをカスタマイズ" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "開始するにはファイルを開いてください" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "サイドバーを展開" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive が未設定です" @@ -3888,15 +3931,17 @@ openFromComputer = "コンピューターから開く" openSettings = "設定を開く" other = "その他" recent = "最近" -search = "検索" -searchPlaceholder = "ファイルを検索..." viewAll = "{{count}} 個のファイルをすべて表示" [fileSidebar.fileItem] closeViewer = "ビューアを閉じる" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "ビューアで開く" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "デフォルトにリセット" subtitle = "ファイルを親カテゴリにグループ化します。既存または新しいラベルをカテゴリに追加したり、名前を変更したり、独自のカテゴリを作成したりできます。どのカテゴリにも属さないファイルは「その他」に表示されます。" title = "サイドバーのカテゴリ" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "ワークスペースに追加" addToWorkspaceCount = "{{count}} 件をワークスペースに追加" @@ -3918,7 +3971,6 @@ backToMyFiles = "マイファイルに戻る" breadcrumbs = "フォルダパス" cancel = "キャンセル" classification = "分類" -clearSearch = "検索をクリア" clearSelection = "選択をクリア" closeDetails = "詳細を閉じる" create = "作成" @@ -3950,6 +4002,7 @@ downloadAll = "すべてダウンロード" downloadVersion = "このバージョンをダウンロード" dropOverlay = "ドロップしてアップロード" dropOverlaySub = "ファイルは最初はローカルに追加されます。フォルダに整理するには『Move to』または『Save to cloud』を使用してください。" +duplicate = "Duplicate" file = "ファイル" fileInfo = "File info" fileMenu = "ファイルの操作" @@ -3983,8 +4036,6 @@ resizeFolderTree = "フォルダツリーのサイズ変更(矢印キー、Shi save = "保存" saveToServer = "サーバーに保存" saveToServerDisabledHint = "このサーバーでサーバーへの保存は有効になっていません。管理者に有効化を依頼してください。" -search = "検索" -searchPlaceholder = "このフォルダとサブフォルダを検索" selectAll = "すべて選択" selectAllHint = "クリックですべて選択。ヒント: Ctrl(または Cmd)でファイルを個別に追加、Shift で範囲選択。" selectedCount = "{{count}} 件を選択" @@ -4044,6 +4095,10 @@ title = "まだクラウドファイルがありません" hint = "アップロードせずに保存したファイルはここに残ります。ファイルをドロップして追加できます。" title = "ローカル専用のファイルはありません" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "開いたり編集したファイルがここに表示されます。" title = "まだ変更はありません" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "フォルダを削除できませんでした。" deleteFolderFailedDetail = "フォルダを削除できませんでした: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "フォルダの外観を更新できませんでした。" folderAppearanceFailedDetail = "フォルダの外観を更新できませんでした: {{message}}" moveFilesFailed = "ファイルを移動できませんでした。" @@ -4113,6 +4170,11 @@ localHint = "このブラウザにのみ保存" shared = "共有" sharedHint = "リンクであなたと共有" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "古い順" modifiedDesc = "新しい順" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "ストレージに利用可能なファイルがあり open = "開く" openFile = "ファイルを開く" openFiles = "複数ファイルを開く" +selectFile = "Select {{name}}" selectFromStorage = "ストレージから選択" upload = "アップロード" uploadFile = "ファイルをアップロード" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "すべて保存済み" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV の抽出に失敗しました" extractXlsxError = "XLSX の抽出に失敗しました" +filled = "filled" flattenAfterFilling = "入力後にフラット化" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "必須" requiredFieldsError = "必須フィールドをすべて入力してください" rescanFields = "フィールドを再スキャン" @@ -4795,9 +4863,6 @@ title = "画像をPDFに変換" [imageToPdf] tags = "conversion,img,jpg,picture,photo,psd,photoshop" -[infoBanner] -dismiss = "閉じる" - [invite] acceptError = "アカウントの作成に失敗しました" accountFor = "次のユーザーのアカウントを作成:" @@ -4979,6 +5044,34 @@ uploadSuccess = "アップロード成功!" uploadSuccessMessage = "画像の転送が完了しました。" validating = "セッションを検証中..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "スキャンして写真をアップロード。画像は自動で PDF に変換されます。" descriptionNoConvert = "モバイルデバイスから写真をアップロードするためにスキャンします。" @@ -4997,8 +5090,13 @@ title = "モバイルからアップロード" tags = "マルチツール,マルチ操作,UI,クリック&ドラッグ,フロントエンド,クライアントサイド,インタラクティブ,操作可能,移動,削除,移行,分割" title = "PDFマルチツール" -[navbar] -search = "検索" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "認証に失敗しました。このウィンドウを閉じて、もう一度お試しください。" @@ -5127,6 +5225,7 @@ activeFiles = "アクティブファイル ビューには、 allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "閉じる" cropSettings = "切り抜きたいファイルを選択したら、PDF を切り抜く範囲を指定するために切り抜きツールを設定できます。" +dialogLabel = "Onboarding" fileCheckbox = "ファイルをクリックすると、処理対象として選択されます。バッチ処理のために複数選択も可能です。" fileReplacement = "変更されたファイルはワークベンチ内で元のファイルを自動的に置き換えるため、さらに別のツールに簡単に回せます。" filesButton = "クイックアクセスバーの ファイル ボタンから、ツールで使用する PDF をアップロードできます。" @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDFをPDF/Aに変換" tags = "アーカイブ,長期,標準,変換,保存,保全" title = "PDFをPDF/Aに変換" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "印刷,標準,変換,制作,プリプレス,アーカイブ" title = "PDF を PDF/X に" @@ -6045,6 +6151,7 @@ small = "500 クレジット" xsmall = "100 クレジット" [plan.availablePlans] +currency = "Billing currency" subtitle = "ニーズに合ったプランを選択" title = "利用可能なプラン" @@ -6624,6 +6731,7 @@ reachedTitle = "月間利用額上限に達しました" title = "Stripe ポータルを開けませんでした" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "{{allowance}} 件の無料PDFのうち使用済み" capSuffix_other = "{{allowance}} 件の無料PDFのうち使用済み" eyebrow = "Processor トライアル" @@ -6873,6 +6981,32 @@ label = "Jira" description = "policy 実行時にドキュメントまたは通知をメール送信します。" label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "処理済みドキュメントを Nextcloud に保存します。" @@ -7077,11 +7211,6 @@ title = "公式 SDKs" beta = "Beta" deprecated = "非推奨" -[portal.docs.search] -empty = "一致するドキュメントはありません" -placeholder = "ドキュメントを検索" -results = "{{count}} 件の結果" - [portal.docs.skills] eyebrow = "スキル" lead = "エージェントが単一ツールとして呼び出す、バンドルされた名前付き機能です。各スキルは評価付きの決定論的な操作チェーンです。" @@ -7180,11 +7309,11 @@ editorAction = "エディター" empty = "このフィルターに一致するドキュメントはありません。" rowActions = "行のアクション" sensitiveLabel = "機密" -sensitiveTitle = "機密 — アクセスが必要" [portal.documents.table.columns] action = "パイプライン / アクション" document = "ドキュメント" +labels = "Labels" product = "製品" status = "ステータス" time = "時刻" @@ -7200,6 +7329,7 @@ host = "ホスト" lastSeen = "最終確認" region = "リージョン" status = "ステータス" +target = "Target" version = "バージョン" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "このビューで予期しないエラーが発生しました retry = "再試行" title = "このページで問題が発生しました" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "戻る" body = "デスクトップアプリをダウンロードするか、サーバーをセルフホストします。" @@ -7380,11 +7560,6 @@ cancel = "キャンセル" confirm = "キーを取り消す" title = "API キーを取り消す" -[portal.infrastructure.attestationLabel] -attested = "証明済み" -inScope = "対象範囲内" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "カテゴリで監査イベントをフィルター" heading = "監査ログ" @@ -7456,11 +7631,6 @@ info = "情報" success = "成功" warning = "警告" -[portal.infrastructure.certLabel] -certified = "認定済み" -inProgress = "進行中" -notStarted = "未開始" - [portal.infrastructure.createKey] cancel = "キャンセル" createKey = "キーを作成" @@ -7474,238 +7644,10 @@ subtitleCreated = "今すぐこのシークレットをコピーしてくださ title = "API キーを作成" titleCreated = "キーが作成されました" -[portal.infrastructure.deployLabel] -live = "ライブ" -queued = "キュー済み" -rolledBack = "ロールバック済み" -rolling = "ロールアウト中" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "デプロイ者" -environment = "環境" -product = "製品" -status = "ステータス" -version = "バージョン" -when = "日時" - -[portal.infrastructure.deployments.recent] -heading = "最近のデプロイメント" -subheading = "製品と環境全体の最新のロールアウト。" - -[portal.infrastructure.deployments.regionColumns] -instances = "インスタンス" -latency = "レイテンシ" -load = "負荷" -p99 = "P99" -region = "リージョン" -status = "ステータス" -throughput = "スループット" -uptime = "稼働時間" -version = "バージョン" - -[portal.infrastructure.deployments.regions] -heading = "リージョン" -subheading = "デプロイ済みのすべての Stirling リージョンのライブ健全性 — レイテンシ、負荷、ロールアウトバージョン。" - -[portal.infrastructure.deployments.regions.empty] -description = "ワークスペースがプロビジョニングされると、デプロイ済みリージョンがここに表示されます。" -title = "デプロイ済みリージョンはありません" - [portal.infrastructure.keyLabel] active = "アクティブ" revoked = "取り消し済み" -[portal.infrastructure.modelLabel] -active = "アクティブ" -degraded = "低下" -disabled = "無効" - -[portal.infrastructure.models] -heading = "モデル" -msValue = "{{value}} ms" -subheading = "ワークスペース全体のドキュメント処理を支えるモデルカタログとルーティング。" - -[portal.infrastructure.models.byom] -description = "オンプレミスまたはセルフホスト型モデルを登録し、データレジデンシー制約のある処理のためにリージョンに固定します。" -title = "独自モデルを持ち込む" - -[portal.infrastructure.models.catalogue] -heading = "カタログ" -sub = "ワークスペースで利用可能なマネージドモデル。ライブのレイテンシとコスト付き。" -subEnterprise = "マネージド、持ち込み、オンプレミスモデル — リージョンごとの固定が利用可能。" - -[portal.infrastructure.models.catalogue.empty] -description = "ワークスペースのカタログ内のモデルがここに表示されます。" -title = "利用可能なモデルはありません" - -[portal.infrastructure.models.columns] -cost = "コスト" -latency = "レイテンシ" -load = "負荷" -model = "モデル" -status = "ステータス" -type = "種類" -version = "バージョン" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/call" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "アクティブモデル" -avgLatency = "平均レイテンシ" -included = "含まれる" -monthlySpend = "月間モデル費用" - -[portal.infrastructure.models.routing] -empty = "ルーティングルールは設定されていません。" -heading = "ルーティングルール" -sub = "各操作をどのモデルが処理するか。より限定的なルールに一致しない場合はデフォルトが適用されます。" -subLocked = "操作を特定のモデルにルーティング — 有料プランで利用可能です。" - -[portal.infrastructure.models.routing.lockedBanner] -description = "Pro にアップグレードすると、各操作とドキュメントタイプをどのモデルが処理するかを制御できます。" -title = "モデルルーティングは有料機能です" - -[portal.infrastructure.models.routingColumns] -default = "デフォルト" -docType = "ドキュメントタイプ" -modelForAria = "{{operation}} のモデル" -operation = "操作" -routedTo = "ルーティング先" - -[portal.infrastructure.modelTypeLabel] -classification = "分類" -extraction = "抽出" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "低下" -down = "停止" -healthy = "正常" - -[portal.infrastructure.security.access.byok] -description = "独自の KMS からキーを提供します。Stirling はそのキーで暗号化しますが、引き続き読み取り可能です。" -label = "独自キーを持ち込む(BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "キーは KMS から外に出ません。Stirling は暗号文のみを保持します。" -label = "独自キーを保持する(HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling が暗号化キーを管理します。最も簡単で、ユーザー側のキー運用は不要です。" -label = "Stirling 保持のキー" - -[portal.infrastructure.security.accessPolicy] -heading = "ドキュメントアクセスポリシー" -subheading = "保存中の処理済みドキュメントを誰が復号できるかを制御します。" - -[portal.infrastructure.security.attestations] -heading = "コンプライアンス証明" -noReport = "利用可能なレポートはありません" -subheading = "フレームワークごとの監査態勢。証明済み統制についてはレポートを利用できます。" -viewReport = "レポートを表示 →" - -[portal.infrastructure.security.compliance] -heading = "コンプライアンス" -subheading = "Stirling プラットフォームを対象とする証明と認証。" - -[portal.infrastructure.security.empty] -description = "ワークスペースのセキュリティ設定がここに表示されます。" -title = "セキュリティ態勢を利用できません" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK では、暗号化キーが KMS から外に出ることはありません。Stirling は暗号文のみを保存および処理し、いつでも取り消すことができます。" -title = "Stirling はドキュメントを復号できません" - -[portal.infrastructure.security.ipAllowlist] -empty = "設定された IP 範囲はありません — すべての IP が許可されています。" -heading = "IP allowlist" -sub = "API アクセスはこれらの CIDR 範囲に制限されています。" -subLocked = "API アクセスを既知の IP 範囲に制限 — 有料プランで利用可能です。" - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Pro にアップグレードすると、API アクセスを特定のネットワークに制限できます。" -title = "IP allowlist は有料機能です" - -[portal.infrastructure.security.ipColumns] -added = "追加日" -addedBy = "追加者" -cidr = "CIDR" -label = "ラベル" - -[portal.infrastructure.security.keyManagement] -algorithm = "アルゴリズム" -heading = "暗号化キー管理" -keyId = "キー識別子" -lastRotated = "最終ローテーション" -rotateKey = "キーをローテーション" -rotationPolicy = "ローテーションポリシー" -subheading = "保存中のドキュメントを暗号化するキーの管理 — 誰が復号できるか、キーをどのようにローテーションするか。" - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key(BYOK)と hold-your-own-key(HYOK)の管理は Enterprise で利用できます。独自の KMS からキーを提供するにはアップグレードしてください。" -title = "お使いのプランではキーは Stirling によって管理されます" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "アジア太平洋" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR データ境界" -label = "欧州連合" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "米国" - -[portal.infrastructure.security.residencyHeader] -heading = "データレジデンシー" -subheading = "ドキュメントが保存および処理される場所。" - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} 使用済み" - -[portal.infrastructure.storage.empty] -description = "接続されたストレージと使用量がここに表示されます。" -title = "ストレージが設定されていません" - -[portal.infrastructure.storage.lifecycle] -active = "アクティブ" -activeRange = "0–{{value}}d" -archived = "アーカイブ済み" -coldStorage = "コールドストレージ" -deleted = "削除済み" -never = "なし" -purged = "パージ済み" - -[portal.infrastructure.storage.providers] -connect = "接続" -connected = "接続済み" -heading = "接続済みプロバイダー" -subheading = "処理済みアーティファクトの書き込み先。" - -[portal.infrastructure.storage.retention] -heading = "保持" -subheading = "ライフサイクル削除までアーティファクトを保持する期間。" -windowLabel = "デフォルトの保持期間" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} 日" -days_other = "{{count}} 日" -never = "削除しない" - -[portal.infrastructure.storage.totalUsage] -heading = "合計使用量" -progressLabel = "使用済みストレージ" -subheading = "接続済みプロバイダー全体で消費されたストレージ。" - [portal.infrastructure.tabs] apiKeys = "API キー" audit = "監査ログ" @@ -7715,11 +7657,9 @@ security = "セキュリティ" storage = "ストレージ" [portal.integrations] -addAnother = "別のものを追加" availableHeading = "利用可能" comingSoonHeading = "近日対応予定" connect = "接続" -connectedHeading = "接続済み" connectionCount_one = "{{count}} 件の接続" connectionCount_other = "{{count}} 件の接続" customApi = "カスタム API" @@ -7737,6 +7677,10 @@ security = "セキュリティ" signing = "署名" storage = "ストレージ" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "接続済み" @@ -7767,7 +7711,6 @@ integrations = "連携" pipelines = "パイプライン" policies = "ポリシー" procurement = "調達" -settings = "設定" sources = "ソース" usage = "使用量と請求" users = "ユーザー" @@ -7784,7 +7727,7 @@ title = "パイプライン" newPipeline = "新しいパイプライン" [portal.pipelines.builder] -addStep = "ツールを追加" +activate = "Activate" back = "パイプラインに戻る" cannotFollow = "{{produced}} を受け取れません" chooseAccount = "アカウントを選択" @@ -7792,28 +7735,38 @@ chooseDestination = "宛先を選択" chooseOperation = "このステップの処理を選択" chooseSource = "ソースを選択" discard = "変更を破棄" -enabled = "有効" inputs = "入力" inputSource = "入力ソース" inputTrigger = "トリガー" keepEditing = "編集を続ける" +moreActions = "More actions" needsConfiguring = "設定が必要です" -needsUpload = "アップロードされたファイルが必要" -noSources = "まだ接続済みのソースがありません。入力として使用するには作成してください。" +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "検索に一致するツールはありません。" -pipelineSettings = "パイプライン設定" +pause = "Pause" +rename = "Rename pipeline" searchTools = "ツールを検索" -selectToolBody = "ツールを追加してパイプラインを構築します。" -selectToolTitle = "ツールはまだありません" sendToSystem = "別のシステムへ送信" stepsIncompatible = "これらのステップは、前のステップが生成するものでは実行できません: {{tools}}。" stepsNeedSetup = "保存する前に、これらのステップの設定がまだ必要です: {{tools}}。" -toolSettings = "ツール設定" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "認識できない操作です。そのまま保持されました。" unsavedBody = "未保存の変更があります。離れる前に保存しますか、それとも破棄しますか?" unsavedTitle = "未保存の変更" -uploadUnsupported = "アップロードされたファイルはまだパイプラインでサポートされていないため、これらのステップは保存できません: {{tools}}。" usesDefaults = "デフォルト設定で実行" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "前のステップからのすべてのファイルを結合します" @@ -7825,31 +7778,36 @@ undeclared-operation = "このステップが受け付けるものを確認で [portal.pipelines.composer] addTool = "ツールを追加" -cancel = "キャンセル" -chainEmpty = "ツールを追加してパイプラインの構築を開始します。" create = "パイプラインを作成" +createPaused = "Create paused" editingUnsupported = "これらのツールパラメーターを編集用に表示することはまだサポートされていません。" -moveDown = "下へ移動" -moveUp = "上へ移動" +editSource = "Edit source" name = "名前" namePlaceholder = "例: Redaction sweep" noToolSettings = "このツールには設定可能な項目がありません。" -operations_one = "操作({{count}})" -operations_other = "操作({{count}})" output = "出力" -removeStep = "操作を削除" save = "変更を保存" scheduleEvery = "実行間隔" -sources = "ソース" -sourcesLoading = "ソースを読み込み中..." trigger = "トリガー" triggerManual = "手動のみ" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "日" hours = "時間" minutes = "分" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\" を削除しますか?この操作は元に戻せません。" cancel = "キャンセル" @@ -7867,6 +7825,37 @@ connectSource = "ソースを接続" description = "最初のパイプラインを作成します: 実行対象のソースを選び、操作を連結し、出力先を選択します。" title = "パイプラインはまだありません" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "アクティブ" paused = "一時停止中" @@ -7879,7 +7868,6 @@ completed_one = "実行が完了しました。" completed_other = "{{count}} 件の実行がすべて完了しました。" empty = "実行するものがありません: ソースに処理対象のドキュメントがありませんでした。" failed = "実行に失敗しました: {{error}}" -historyCleared = "履歴をクリアしました。次回の実行では、現在ソース内にあるすべてを再処理します。" inFlight = "新しく実行するものがありません: 以前の実行のドキュメントがまだ処理中です。" parked_one = "実行するものがありません: {{count}} 件のドキュメントは以前に失敗して保留中です。原因を修正してから、履歴をクリアして再試行してください。" parked_other = "実行するものがありません: {{count}} 件のドキュメントは以前に失敗して保留中です。原因を修正してから、履歴をクリアして再試行してください。" @@ -7895,6 +7883,7 @@ name = "パイプライン" sources = "ソース" status = "ステータス" steps = "ステップ" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "フォルダー監視" @@ -8209,6 +8198,24 @@ label = "Jira 課題に添付" description = "処理済みドキュメントを添付ファイルとして送信します。" label = "ドキュメントをメール送信(Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "処理済みドキュメントをフォルダーに書き込みます。" label = "Nextcloud にアップロード" @@ -8683,16 +8690,6 @@ maxed = "上限に達しました" subtitle = "無料トライアルは {{date}} まで有効です。カードは不要です。" title = "Enterprise トライアル" -[portal.search] -ariaLabel = "検索" -placeholder = "Stirling を検索 — エンドポイント、パイプライン、ドキュメント…" - -[portal.search.empty] -noActionsDescription = "利用可能になると、クイックアクションがここに表示されます。" -noActionsTitle = "クイックアクションはありません" -noMatches = "「{{query}}」に一致するものはありません" -noMatchesDescription = "別のキーワードを試すか、カタログを参照してください。" - [portal.settings.groups] admin = "管理者" @@ -8743,10 +8740,6 @@ cancel = "キャンセル" confirm = "削除" title = "ソースを削除しますか?" -[portal.sources.empty] -description = "フォルダー(まもなくクラウドストレージも)を接続して、ポリシーがドキュメントを取得できる場所を用意します。" -title = "まだソースが接続されていません" - [portal.sources.kpi] inUse = "使用中" total = "接続" @@ -8785,6 +8778,7 @@ unused = "未使用" documents = "ドキュメント" source = "ソース" status = "ステータス" +type = "Type" usedBy = "ポリシー" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "ポリシーとプライバシー" [settings.preferences] title = "環境設定" -[settings.search] -placeholder = "設定ページを検索..." - [settings.security] description = "アカウントを安全に保つため、パスワードを更新してください。" title = "セキュリティ" @@ -10128,6 +10119,7 @@ last = "最後のページ" maintainRatio = "アスペクト比を維持を切替え" next = "次のページ" noSavedSigs = "保存された署名が見つかりません" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "個人署名" previous = "前のページ" redo = "やり直す" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "背景の削除に失敗" hint = "署名の PNG または JPG 画像をアップロードしてください" label = "署名画像をアップロード" placeholder = "画像ファイルを選択" +previewAlt = "Current image signature" processing = "画像を処理中..." removeBackground = "白背景を削除(透過化)" @@ -10179,6 +10172,17 @@ saved = "上の保存済み署名を選択し、PDF上の任意の場所をク text = "上で氏名を入力した後、PDF 上の任意の場所をクリックして署名を配置します。" title = "署名の追加方法" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "署名を移動" pause = "配置を一時停止" @@ -10625,6 +10629,23 @@ title = "サーバーにアップロード" updateButton = "サーバー上で更新" uploadButton = "サーバーにアップロード" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "戻る" pdfTools = "PDF ツール" placeholder = "ツールを選択して開始" premiumFeature = "プレミアム機能:" -searchTools = "ツールを検索" toolsHeader = "ツール" viewAllTools = "すべてのツールを表示" @@ -10822,6 +10842,9 @@ removal = "削除" signing = "署名" verification = "検証" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "ツールが見つかりません" noTools = "利用可能なツールがありません" @@ -10979,6 +11002,13 @@ approver = "ポリシーを承認" editor = "エディター" processor = "プロセッサー" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "{{email}} への招待をキャンセルしますか?現在のリンクでは参加できなくなります。" cancelInviteTitle = "招待をキャンセル" @@ -10998,10 +11028,8 @@ title = "まだメンバーはいません" addToTeam = "チームに追加" guestCount = "{{count}}人のゲスト" guests = "ゲスト" -guestsDesc = "外部共同作業者。共有された範囲に限定されます。エディターのみ。" ledBy = "{{owner}} が主導" org = "組織" -orgDesc = "組織全体の権限とポリシー承認権を持つオーナー" owners = "{{count}}人のオーナー" team = "{{name}} チーム" teamMeta = "{{count}}人" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "{{who}} による招待" cancel = "キャンセル" -count = "{{count}}件保留中" -desc = "まだ参加していない招待済みのユーザーです。承諾するまでシートを保持します。" expiresInDays_one = "{{count}}日後に期限切れ" expiresInDays_other = "{{count}}日後に期限切れ" expiresToday = "今日期限切れ" title = "保留中の招待" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "バックエンドへの接続で問題が発生したか、アクセス権がありません。もう一度お試しください。" title = "メンバーを読み込めませんでした" @@ -11237,12 +11267,15 @@ searchPlaceholder = "添付ファイルを検索" title = "添付ファイル" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "ブックマークのタイトル" bookmarkTitleRequired = "ブックマークのタイトルは必須です" closeSidebar = "ブックマークのサイドバーを閉じる" collapseAll = "すべてのブックマークを折りたたむ" +empty = "No bookmarks in this document" expandAll = "すべてのブックマークを展開" searchPlaceholder = "ブックマークを検索" +title = "Bookmarks" [viewer.comments] addComment = "コメントを追加" @@ -11264,10 +11297,12 @@ locateAnnotation = "ドキュメント内で表示" moreActions = "その他の操作" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "ページ {{page}}" placingHint = "配置するにはページをクリック…(キャンセル)" removeCommentOnly = "コメントのみ削除" saveReply = "返信を保存" +searchPlaceholder = "Search comments" title = "コメント" typeComment = "コメント" typeInsertText = "テキストを挿入" @@ -11290,6 +11325,7 @@ unsavedDesc = "未保存の変更があります" closeSidebar = "レイヤーのサイドバーを閉じる" hideAll = "すべてのレイヤーを非表示" showAll = "すべてのレイヤーを表示" +title = "Layers" [viewer.link] delete = "リンクを削除" @@ -11646,6 +11682,7 @@ exitRedaction = "墨消しモードを終了" exportAll = "PDF をエクスポート" exportSelected = "選択したページをエクスポート" formFill = "フォーム入力" +hideToolbar = "Hide toolbar" multiTool = "マルチツール" panMode = "パンモード" print = "PDF を印刷" @@ -11666,6 +11703,7 @@ selectAll = "すべて選択" selectByNumber = "ページ番号で選択" selectLanguage = "言語を選択" share = "共有" +showToolbar = "Show toolbar" toggleAnnotations = "注釈の表示を切り替え" toggleAttachments = "添付ファイルを切り替え" toggleBookmarks = "ブックマークを切り替え" diff --git a/frontend/editor/public/locales/ko-KR/translation.toml b/frontend/editor/public/locales/ko-KR/translation.toml index 067de0c365..415ef009f1 100644 --- a/frontend/editor/public/locales/ko-KR/translation.toml +++ b/frontend/editor/public/locales/ko-KR/translation.toml @@ -1,7 +1,6 @@ alphabet = "알파벳" apply = "적용" applyAndContinue = "저장하고 나가기" -areYouSure = "정말 떠나시겠습니까?" back = "뒤로" black = "검정" blue = "파랑" @@ -106,6 +105,7 @@ undoSuccess = "작업을 성공적으로 취소했습니다" unknown = "알 수 없음" unpin = "파일 고정 해제(도구 실행 후 교체)" unsavedChanges = "PDF에 저장되지 않은 변경 사항이 있습니다." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "저장되지 않은 변경 사항" unsupported = "지원되지 않음" username = "사용자 이름" @@ -1879,6 +1879,9 @@ width = "너비" [app] description = "무료 Adobe Acrobat 대안(1,000만+ 다운로드)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "PDF/A-3b로 변환" convertToPdfA3bDescription = "첨부 파일이 포함된 보관용 PDF를 생성합니다" @@ -2933,12 +2936,16 @@ text = "이 권한을 변경할 수 없도록 하려면, 비밀번호 추가 도 [chat.actions] copy = "메시지 복사" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling 에이전트 옵션" +assistant = "AI Assistant" clearChat = "채팅 지우기" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "이 작업은 클라우드 크레딧을 사용합니다" tooltip = "화면에서 색상 선택" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "색상 선택" [common] +actions = "Actions" back = "뒤로" cancel = "취소" close = "닫기" +codeSample = "Code sample" collapse = "접기" confirm = "확인" continue = "계속" copied = "복사됨!" copy = "복사" done = "완료" +enterValue = "Enter value" error = "오류" expand = "펼치기" loading = "불러오는 중..." next = "다음" +open = "Open" preview = "미리보기" previous = "이전" refresh = "새로고침" remaining = "남음" retry = "다시 시도" save = "저장" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "선택 항목 지우기" @@ -3185,6 +3199,7 @@ title = "압축 방식" [compress.settings] desiredSize = "원하는 파일 크기" desiredSizePlaceholder = "크기 입력" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "압축은 파일 크기를 줄이는 쉬운 방법입니다. 파일 크기를 선택하면 목표 크기를 입력하고 품질은 저희가 조정합니다. 품질을 선택하면 압축 강도를 직접 설정할 수 있습니다." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF 옵션" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "먼저 원본 형식을 선택하세요" settings = "설정" single = "단일" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "검색..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "편집" @@ -3824,11 +3861,13 @@ shareSelected = "선택 항목 공유" sharing = "공유" showAll = "모두 표시" showHistory = "기록 표시" +sortBy = "Sort files" sortByDate = "날짜순 정렬" sortByName = "이름순 정렬" sortBySize = "크기순 정렬" storage = "저장소" storageState = "저장소" +storageUsed = "Storage used" synced = "동기화됨" title = "PDF 파일 업로드" toolChain = "적용된 도구" @@ -3874,8 +3913,12 @@ addFiles = "파일 추가" addingFiles = "파일 추가 중…" collapse = "사이드바 접기" customizeGroups = "그룹 사용자 지정" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "시작하려면 파일을 여세요" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "사이드바 펼치기" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive가 구성되지 않았습니다" @@ -3888,15 +3931,17 @@ openFromComputer = "컴퓨터에서 열기" openSettings = "설정 열기" other = "기타" recent = "최근" -search = "검색" -searchPlaceholder = "파일 검색..." viewAll = "{{count}}개 파일 모두 보기" [fileSidebar.fileItem] closeViewer = "뷰어 닫기" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "뷰어에서 열기" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "기본값으로 재설정" subtitle = "파일을 상위 카테고리로 그룹화하세요. 기존 또는 새 라벨을 카테고리에 추가하거나, 이름을 변경하거나, 직접 만들 수 있습니다. 어떤 카테고리에도 속하지 않는 파일은 “기타” 아래에 표시됩니다." title = "사이드바 카테고리" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "작업 공간에 추가" addToWorkspaceCount = "{{count}}개를 작업 공간에 추가" @@ -3918,7 +3971,6 @@ backToMyFiles = "내 파일로 돌아가기" breadcrumbs = "폴더 경로" cancel = "취소" classification = "분류" -clearSearch = "검색 지우기" clearSelection = "선택 해제" closeDetails = "세부정보 닫기" create = "생성" @@ -3950,6 +4002,7 @@ downloadAll = "전체 다운로드" downloadVersion = "이 버전 다운로드" dropOverlay = "업로드하려면 파일을 놓으세요" dropOverlaySub = "파일은 로컬에서 시작됩니다. '이동' 또는 '클라우드에 저장'으로 폴더에 정리하세요." +duplicate = "Duplicate" file = "파일" fileInfo = "File info" fileMenu = "파일 작업" @@ -3983,8 +4036,6 @@ resizeFolderTree = "폴더 트리 크기 조절(화살표 키, 크게 조절은 save = "저장" saveToServer = "서버에 저장" saveToServerDisabledHint = "이 서버에서는 서버로 저장이 활성화되어 있지 않습니다. 관리자에게 활성화를 요청하세요." -search = "검색" -searchPlaceholder = "이 폴더 및 하위 폴더 검색" selectAll = "모두 선택" selectAllHint = "모두 선택하려면 클릭. 팁: Ctrl(또는 Cmd)로 하나씩 추가, Shift로 범위 선택." selectedCount = "{{count}}개 선택됨" @@ -4044,6 +4095,10 @@ title = "아직 클라우드 파일이 없습니다" hint = "업로드하지 않고 저장한 파일은 여기에 남습니다. 파일을 놓아 추가하세요." title = "로컬 전용 파일 없음" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "열거나 편집한 파일이 여기에 표시됩니다." title = "아직 수정된 항목이 없습니다" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "폴더를 삭제할 수 없습니다." deleteFolderFailedDetail = "폴더를 삭제할 수 없습니다: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "폴더 모양을 업데이트할 수 없습니다." folderAppearanceFailedDetail = "폴더 모양을 업데이트할 수 없습니다: {{message}}" moveFilesFailed = "파일을 이동할 수 없습니다." @@ -4113,6 +4170,11 @@ localHint = "이 브라우저에만 저장됨" shared = "공유됨" sharedHint = "링크로 공유됨" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "오래된 순" modifiedDesc = "최신 순" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "저장소에 파일이 없습니다. 먼저 파일을 open = "열기" openFile = "파일 열기" openFiles = "여러 파일 열기" +selectFile = "Select {{name}}" selectFromStorage = "저장소에서 선택" upload = "업로드" uploadFile = "파일 업로드" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "모두 저장됨" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV 추출 실패" extractXlsxError = "XLSX 추출 실패" +filled = "filled" flattenAfterFilling = "채운 후 평면화" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "필수" requiredFieldsError = "모든 필수 필드를 입력하세요" rescanFields = "필드 재검색" @@ -4795,9 +4863,6 @@ title = "이미지를 PDF로" [imageToPdf] tags = "변환,이미지,jpg,사진" -[infoBanner] -dismiss = "닫기" - [invite] acceptError = "계정 생성에 실패했습니다" accountFor = "계정 생성 대상" @@ -4979,6 +5044,34 @@ uploadSuccess = "업로드 성공!" uploadSuccessMessage = "이미지가 전송되었습니다." validating = "세션 검증 중..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "스캔하여 사진을 업로드하세요. 이미지는 자동으로 PDF로 변환됩니다." descriptionNoConvert = "모바일 기기에서 사진을 스캔해 업로드하세요." @@ -4997,8 +5090,13 @@ title = "모바일에서 업로드" tags = "멀티 도구,다중 작업,UI,클릭 드래그,프론트엔드,클라이언트 사이드,대화형,상호작용,이동,삭제,마이그레이션,분할" title = "PDF 멀티 도구" -[navbar] -search = "검색" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "인증에 성공하지 못했습니다. 이 창을 닫고 다시 시도하세요." @@ -5127,6 +5225,7 @@ activeFiles = "활성 파일 보기에는 도구에 로드한 allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "닫기" cropSettings = "이제 자르려는 파일을 선택했으므로, 자르기 도구를 구성하여 PDF를 자를 영역을 지정할 수 있습니다." +dialogLabel = "Onboarding" fileCheckbox = "파일을 클릭하면 처리 대상으로 선택됩니다. 배치 작업을 위해 여러 파일을 선택할 수 있습니다." fileReplacement = "수정된 파일은 워크벤치에서 원본 파일을 자동으로 대체하므로, 더 많은 도구로 쉽게 이어서 작업할 수 있습니다." filesButton = "빠른 액세스 막대의 파일 버튼을 사용하면 도구로 처리할 PDF를 업로드할 수 있습니다." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF를 PDF/A로" tags = "아카이브,장기,표준,변환,저장,보존" title = "PDF를 PDF/A로" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "인쇄,표준,변환,제작,프리프레스,보관" title = "PDF를 PDF/X로" @@ -6045,6 +6151,7 @@ small = "500 크레딧" xsmall = "100 크레딧" [plan.availablePlans] +currency = "Billing currency" subtitle = "필요에 맞는 요금제를 선택하세요" title = "이용 가능한 요금제" @@ -6624,6 +6731,7 @@ reachedTitle = "월별 지출 한도에 도달했습니다" title = "Stripe portal을 열 수 없음" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "무료 PDFs {{allowance}}개 중 사용됨" capSuffix_other = "무료 PDFs {{allowance}}개 중 사용됨" eyebrow = "Processor 평가판" @@ -6873,6 +6981,32 @@ label = "Jira" description = "정책이 실행될 때 문서 또는 알림을 Email로 보냅니다." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "처리된 문서를 Nextcloud에 파일로 저장합니다." @@ -7077,11 +7211,6 @@ title = "공식 SDKs" beta = "Beta" deprecated = "Deprecated" -[portal.docs.search] -empty = "일치하는 문서 없음" -placeholder = "문서 검색" -results = "결과 {{count}}개" - [portal.docs.skills] eyebrow = "SKILLS" lead = "agent가 단일 도구로 호출하는 번들형 명명된 기능입니다. 각 skill은 eval이 연결된 결정적 작업 체인입니다." @@ -7180,11 +7309,11 @@ editorAction = "편집자" empty = "이 필터와 일치하는 문서가 없습니다." rowActions = "행 작업" sensitiveLabel = "민감" -sensitiveTitle = "민감 — 접근 필요" [portal.documents.table.columns] action = "파이프라인 / 작업" document = "문서" +labels = "Labels" product = "제품" status = "상태" time = "시간" @@ -7200,6 +7329,7 @@ host = "호스트" lastSeen = "마지막 확인" region = "지역" status = "상태" +target = "Target" version = "버전" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "이 보기에서 예기치 않은 오류가 발생했습니다. retry = "다시 시도" title = "이 페이지에서 문제가 발생했습니다" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "뒤로" body = "데스크톱 앱을 다운로드하거나 서버를 자체 호스팅하세요." @@ -7380,11 +7560,6 @@ cancel = "취소" confirm = "키 폐기" title = "API 키 폐기" -[portal.infrastructure.attestationLabel] -attested = "증명됨" -inScope = "범위 내" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "카테고리별 감사 이벤트 필터링" heading = "감사 로그" @@ -7456,11 +7631,6 @@ info = "정보" success = "성공" warning = "경고" -[portal.infrastructure.certLabel] -certified = "인증됨" -inProgress = "진행 중" -notStarted = "시작 안 됨" - [portal.infrastructure.createKey] cancel = "취소" createKey = "키 생성" @@ -7474,238 +7644,10 @@ subtitleCreated = "지금 이 비밀을 복사하세요 — 다시 표시되지 title = "API 키 생성" titleCreated = "키 생성됨" -[portal.infrastructure.deployLabel] -live = "라이브" -queued = "대기 중" -rolledBack = "롤백됨" -rolling = "롤아웃 중" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "배포자" -environment = "환경" -product = "제품" -status = "상태" -version = "버전" -when = "시점" - -[portal.infrastructure.deployments.recent] -heading = "최근 배포" -subheading = "제품 및 환경 전반의 최신 롤아웃입니다." - -[portal.infrastructure.deployments.regionColumns] -instances = "인스턴스" -latency = "지연 시간" -load = "부하" -p99 = "P99" -region = "지역" -status = "상태" -throughput = "처리량" -uptime = "가동 시간" -version = "버전" - -[portal.infrastructure.deployments.regions] -heading = "지역" -subheading = "배포된 모든 Stirling 지역의 실시간 상태 — 지연 시간, 부하, 롤아웃 버전." - -[portal.infrastructure.deployments.regions.empty] -description = "워크스페이스가 프로비저닝되면 배포된 지역이 여기에 표시됩니다." -title = "배포된 지역 없음" - [portal.infrastructure.keyLabel] active = "활성" revoked = "폐기됨" -[portal.infrastructure.modelLabel] -active = "활성" -degraded = "성능 저하" -disabled = "비활성화됨" - -[portal.infrastructure.models] -heading = "모델" -msValue = "{{value}} ms" -subheading = "워크스페이스 전반의 문서 처리를 구동하는 모델 카탈로그 및 라우팅입니다." - -[portal.infrastructure.models.byom] -description = "온프레미스 또는 셀프 호스팅 모델을 등록하고 데이터 레지던시 기반 처리를 위해 지역에 고정하세요." -title = "자체 모델 가져오기" - -[portal.infrastructure.models.catalogue] -heading = "카탈로그" -sub = "워크스페이스에서 사용할 수 있는 관리형 모델이며, 실시간 지연 시간과 비용을 포함합니다." -subEnterprise = "관리형, 자체 제공, 온프레미스 모델 — 지역별 고정 사용 가능." - -[portal.infrastructure.models.catalogue.empty] -description = "워크스페이스 카탈로그의 모델이 여기에 표시됩니다." -title = "사용 가능한 모델 없음" - -[portal.infrastructure.models.columns] -cost = "비용" -latency = "지연 시간" -load = "부하" -model = "모델" -status = "상태" -type = "유형" -version = "버전" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/호출" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "활성 모델" -avgLatency = "평균 지연 시간" -included = "포함됨" -monthlySpend = "월별 모델 지출" - -[portal.infrastructure.models.routing] -empty = "구성된 라우팅 규칙이 없습니다." -heading = "라우팅 규칙" -sub = "각 작업을 처리할 모델입니다. 더 구체적인 규칙과 일치하지 않을 때 기본값이 적용됩니다." -subLocked = "작업을 특정 모델로 라우팅 — 유료 플랜에서 사용 가능." - -[portal.infrastructure.models.routing.lockedBanner] -description = "각 작업과 문서 유형을 처리할 모델을 제어하려면 Pro로 업그레이드하세요." -title = "모델 라우팅은 유료 기능입니다" - -[portal.infrastructure.models.routingColumns] -default = "기본값" -docType = "문서 유형" -modelForAria = "{{operation}}용 모델" -operation = "작업" -routedTo = "라우팅 대상" - -[portal.infrastructure.modelTypeLabel] -classification = "분류" -extraction = "추출" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "성능 저하" -down = "중단" -healthy = "정상" - -[portal.infrastructure.security.access.byok] -description = "자체 KMS의 키를 제공하세요. Stirling은 이 키로 암호화하지만 여전히 읽을 수 있습니다." -label = "자체 키 가져오기(BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "키는 KMS를 벗어나지 않습니다. Stirling은 암호문만 보유합니다." -label = "자체 키 보유(HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling이 암호화 키를 관리합니다. 가장 간단하며 사용자가 키 작업을 할 필요가 없습니다." -label = "Stirling 보유 키" - -[portal.infrastructure.security.accessPolicy] -heading = "문서 접근 정책" -subheading = "저장된 처리 완료 문서를 복호화할 수 있는 사용자를 제어합니다." - -[portal.infrastructure.security.attestations] -heading = "컴플라이언스 증명" -noReport = "사용 가능한 보고서 없음" -subheading = "증명된 제어에 대한 보고서가 제공되는 프레임워크별 감사 상태입니다." -viewReport = "보고서 보기 →" - -[portal.infrastructure.security.compliance] -heading = "컴플라이언스" -subheading = "Stirling 플랫폼을 포괄하는 증명 및 인증입니다." - -[portal.infrastructure.security.empty] -description = "워크스페이스의 보안 구성이 여기에 표시됩니다." -title = "보안 상태를 사용할 수 없음" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK를 사용하면 암호화 키가 KMS를 벗어나지 않습니다. Stirling은 언제든지 취소할 수 있는 암호문만 저장하고 처리합니다." -title = "Stirling은 문서를 복호화할 수 없습니다" - -[portal.infrastructure.security.ipAllowlist] -empty = "구성된 IP 범위가 없습니다 — 모든 IP가 허용됩니다." -heading = "IP 허용 목록" -sub = "API 접근이 이러한 CIDR 범위로 제한됩니다." -subLocked = "API 접근을 알려진 IP 범위로 제한 — 유료 플랜에서 사용 가능." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "API 접근을 특정 네트워크로 제한하려면 Pro로 업그레이드하세요." -title = "IP 허용 목록은 유료 기능입니다" - -[portal.infrastructure.security.ipColumns] -added = "추가됨" -addedBy = "추가한 사람" -cidr = "CIDR" -label = "레이블" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "암호화 키 관리" -keyId = "키 식별자" -lastRotated = "마지막 교체" -rotateKey = "키 교체" -rotationPolicy = "교체 정책" -subheading = "저장된 문서를 암호화하는 키의 보관 관리 — 복호화 가능 주체 및 키 교체 방식." - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key(BYOK) 및 hold-your-own-key(HYOK) 보관 관리는 Enterprise에서 사용할 수 있습니다. 자체 KMS의 키를 제공하려면 업그레이드하세요." -title = "현재 플랜에서는 키가 Stirling에서 관리됩니다" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "아시아 태평양" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR 데이터 경계" -label = "유럽 연합" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "미국" - -[portal.infrastructure.security.residencyHeader] -heading = "데이터 레지던시" -subheading = "문서가 저장되고 처리되는 위치입니다." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} 사용됨" - -[portal.infrastructure.storage.empty] -description = "연결된 스토리지와 사용량이 여기에 표시됩니다." -title = "구성된 스토리지 없음" - -[portal.infrastructure.storage.lifecycle] -active = "활성" -activeRange = "0–{{value}}d" -archived = "보관됨" -coldStorage = "cold storage" -deleted = "삭제됨" -never = "없음" -purged = "영구 삭제됨" - -[portal.infrastructure.storage.providers] -connect = "연결" -connected = "연결됨" -heading = "연결된 제공업체" -subheading = "처리된 아티팩트가 기록되는 위치입니다." - -[portal.infrastructure.storage.retention] -heading = "보존" -subheading = "수명 주기 삭제 전 아티팩트가 보관되는 기간입니다." -windowLabel = "기본 보존 기간" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}}일" -days_other = "{{count}}일" -never = "삭제하지 않음" - -[portal.infrastructure.storage.totalUsage] -heading = "총 사용량" -progressLabel = "사용된 스토리지" -subheading = "연결된 모든 제공업체에서 소비된 스토리지입니다." - [portal.infrastructure.tabs] apiKeys = "API 키" audit = "감사 로그" @@ -7715,11 +7657,9 @@ security = "보안" storage = "스토리지" [portal.integrations] -addAnother = "다른 항목 추가" availableHeading = "사용 가능" comingSoonHeading = "곧 제공 예정" connect = "연결" -connectedHeading = "연결됨" connectionCount_one = "{{count}}개 연결" connectionCount_other = "{{count}}개 연결" customApi = "사용자 지정 API" @@ -7737,6 +7677,10 @@ security = "보안" signing = "서명" storage = "스토리지" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "연결됨" @@ -7767,7 +7711,6 @@ integrations = "통합" pipelines = "파이프라인" policies = "정책" procurement = "구매" -settings = "설정" sources = "소스" usage = "사용량 및 청구" users = "사용자" @@ -7784,7 +7727,7 @@ title = "파이프라인" newPipeline = "새 파이프라인" [portal.pipelines.builder] -addStep = "도구 추가" +activate = "Activate" back = "파이프라인으로 돌아가기" cannotFollow = "{{produced}}을(를) 사용할 수 없습니다" chooseAccount = "계정 선택" @@ -7792,28 +7735,38 @@ chooseDestination = "대상 선택" chooseOperation = "이 단계에서 수행할 작업 선택" chooseSource = "소스 선택" discard = "변경 사항 버리기" -enabled = "활성화됨" inputs = "입력" inputSource = "입력 소스" inputTrigger = "트리거" keepEditing = "계속 편집" +moreActions = "More actions" needsConfiguring = "설정 필요" -needsUpload = "업로드된 파일 필요" -noSources = "아직 연결된 소스가 없습니다. 입력으로 사용하려면 하나를 생성하세요." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "검색과 일치하는 도구가 없습니다." -pipelineSettings = "파이프라인 설정" +pause = "Pause" +rename = "Rename pipeline" searchTools = "도구 검색" -selectToolBody = "파이프라인을 구축하려면 도구를 추가하세요." -selectToolTitle = "아직 도구가 없습니다" sendToSystem = "다른 시스템으로 보내기" stepsIncompatible = "이 단계들은 이전 단계가 생성한 항목에서 실행할 수 없습니다: {{tools}}." stepsNeedSetup = "저장하기 전에 아직 설정이 필요한 단계입니다: {{tools}}." -toolSettings = "도구 설정" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "인식되지 않는 작업이며, 그대로 유지되었습니다." unsavedBody = "저장되지 않은 변경 사항이 있습니다. 나가기 전에 저장하시겠습니까, 아니면 버리시겠습니까?" unsavedTitle = "저장되지 않은 변경 사항" -uploadUnsupported = "업로드된 파일은 아직 파이프라인에서 지원되지 않으므로 다음 단계는 저장할 수 없습니다: {{tools}}." usesDefaults = "기본 설정으로 실행" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "이전 단계의 모든 파일을 결합합니다" @@ -7825,31 +7778,36 @@ undeclared-operation = "이 단계가 무엇을 허용하는지 확인할 수 [portal.pipelines.composer] addTool = "도구 추가" -cancel = "취소" -chainEmpty = "파이프라인 구축을 시작하려면 도구를 추가하세요." create = "파이프라인 생성" +createPaused = "Create paused" editingUnsupported = "편집을 위해 이러한 도구 매개변수를 표시하는 기능은 아직 지원되지 않습니다." -moveDown = "아래로 이동" -moveUp = "위로 이동" +editSource = "Edit source" name = "이름" namePlaceholder = "예: Redaction sweep" noToolSettings = "이 도구에는 구성 가능한 설정이 없습니다." -operations_one = "작업({{count}})" -operations_other = "작업({{count}})" output = "출력" -removeStep = "작업 제거" save = "변경 사항 저장" scheduleEvery = "실행 주기" -sources = "소스" -sourcesLoading = "소스 로드 중..." trigger = "트리거" triggerManual = "수동만" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "일" hours = "시간" minutes = "분" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\"을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다." cancel = "취소" @@ -7867,6 +7825,37 @@ connectSource = "소스 연결" description = "첫 번째 파이프라인을 생성하세요. 실행할 소스를 선택하고, 작업을 연결하며, 출력 위치를 선택하세요." title = "아직 파이프라인이 없습니다" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "활성" paused = "일시 중지됨" @@ -7879,7 +7868,6 @@ completed_one = "실행이 완료되었습니다." completed_other = "{{count}}개 실행이 모두 완료되었습니다." empty = "실행할 항목이 없습니다. 소스에 처리할 문서가 없습니다." failed = "실행 실패: {{error}}" -historyCleared = "기록이 지워졌습니다. 다음 실행에서는 현재 소스에 있는 모든 항목을 다시 처리합니다." inFlight = "새로 실행할 항목이 없습니다: 이전 실행의 문서가 아직 처리 중입니다." parked_one = "실행할 항목이 없습니다: 문서 {{count}}개가 이전에 실패하여 보류되어 있습니다. 원인을 수정한 다음 기록을 지워 다시 시도하세요." parked_other = "실행할 항목이 없습니다: 문서 {{count}}개가 이전에 실패하여 보류되어 있습니다. 원인을 수정한 다음 기록을 지워 다시 시도하세요." @@ -7895,6 +7883,7 @@ name = "파이프라인" sources = "소스" status = "상태" steps = "단계" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "폴더 감시" @@ -8209,6 +8198,24 @@ label = "Jira 이슈에 첨부" description = "처리된 문서를 첨부 파일로 보냅니다." label = "문서 이메일 보내기(Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "처리된 문서를 폴더에 기록합니다." label = "Nextcloud에 업로드" @@ -8683,16 +8690,6 @@ maxed = "한도 도달" subtitle = "무료 평가판은 {{date}}까지 진행됩니다. 카드가 필요하지 않습니다." title = "Enterprise 평가판" -[portal.search] -ariaLabel = "검색" -placeholder = "Stirling 검색 — 엔드포인트, 파이프라인, 문서…" - -[portal.search.empty] -noActionsDescription = "빠른 작업을 사용할 수 있게 되면 여기에 표시됩니다." -noActionsTitle = "빠른 작업 없음" -noMatches = "\"{{query}}\"에 대한 일치 항목 없음" -noMatchesDescription = "다른 키워드를 시도하거나 카탈로그를 찾아보세요." - [portal.settings.groups] admin = "관리자" @@ -8743,10 +8740,6 @@ cancel = "취소" confirm = "삭제" title = "소스를 삭제하시겠습니까?" -[portal.sources.empty] -description = "정책이 문서를 가져올 위치를 가질 수 있도록 폴더(곧 cloud storage도)를 연결하세요." -title = "아직 연결된 소스 없음" - [portal.sources.kpi] inUse = "사용 중" total = "연결" @@ -8785,6 +8778,7 @@ unused = "미사용" documents = "문서" source = "소스" status = "상태" +type = "Type" usedBy = "정책" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "정책 및 개인정보 보호" [settings.preferences] title = "환경설정" -[settings.search] -placeholder = "설정 페이지 검색..." - [settings.security] description = "계정을 안전하게 유지하려면 비밀번호를 업데이트하세요." title = "보안" @@ -10128,6 +10119,7 @@ last = "마지막 페이지" maintainRatio = "종횡비 유지 토글" next = "다음 페이지" noSavedSigs = "저장된 서명이 없습니다" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "개인 서명" previous = "이전 페이지" redo = "다시 실행" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "배경 제거 실패" hint = "서명 PNG 또는 JPG 이미지를 업로드하세요" label = "서명 이미지 업로드" placeholder = "이미지 파일 선택" +previewAlt = "Current image signature" processing = "이미지 처리 중..." removeBackground = "흰색 배경 제거(투명 처리)" @@ -10179,6 +10172,17 @@ saved = "위에서 저장된 서명을 선택한 뒤, PDF를 클릭하여 배치 text = "위에 이름을 입력한 후, PDF의 아무 곳이나 클릭하여 서명을 배치하세요." title = "서명 추가 방법" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "서명 이동" pause = "배치 일시중지" @@ -10625,6 +10629,23 @@ title = "서버로 업로드" updateButton = "서버에서 업데이트" uploadButton = "서버로 업로드" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "뒤로 가기" pdfTools = "PDF 도구" placeholder = "시작하려면 도구를 선택하세요" premiumFeature = "프리미엄 기능:" -searchTools = "도구 검색" toolsHeader = "도구" viewAllTools = "모든 도구 보기" @@ -10822,6 +10842,9 @@ removal = "제거" signing = "서명" verification = "검증" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "도구를 찾을 수 없습니다" noTools = "사용 가능한 도구가 없습니다" @@ -10979,6 +11002,13 @@ approver = "정책 승인" editor = "편집기" processor = "프로세서" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "{{email}}에 대한 초대를 취소하시겠습니까? 현재 링크로는 참여할 수 없게 됩니다." cancelInviteTitle = "초대 취소" @@ -10998,10 +11028,8 @@ title = "아직 구성원 없음" addToTeam = "팀에 추가" guestCount = "{{count}}명 게스트" guests = "게스트" -guestsDesc = "외부 협업자, 공유한 범위로 제한됨. 편집기만 사용." ledBy = "{{owner}}이(가) 이끎" org = "조직" -orgDesc = "조직 전체 권한 및 정책 승인을 가진 소유자" owners = "{{count}}명 소유자" team = "{{name}} 팀" teamMeta = "{{count}}명" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "{{who}}님이 초대함" cancel = "취소" -count = "{{count}}개 대기 중" -desc = "아직 참여하지 않은 초대받은 사람들입니다. 수락할 때까지 시트를 차지합니다." expiresInDays_one = "{{count}}일 후 만료" expiresInDays_other = "{{count}}일 후 만료" expiresToday = "오늘 만료" title = "대기 중인 초대" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "백엔드에 연결하는 중 문제가 발생했거나 접근 권한이 없습니다. 다시 시도하세요." title = "멤버를 불러올 수 없습니다" @@ -11237,12 +11267,15 @@ searchPlaceholder = "첨부 파일 검색" title = "첨부 파일" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "북마크 제목" bookmarkTitleRequired = "북마크 제목은 필수입니다" closeSidebar = "북마크 사이드바 닫기" collapseAll = "모든 북마크 접기" +empty = "No bookmarks in this document" expandAll = "모든 북마크 펼치기" searchPlaceholder = "북마크 검색" +title = "Bookmarks" [viewer.comments] addComment = "댓글 추가" @@ -11264,10 +11297,12 @@ locateAnnotation = "문서에서 찾기" moreActions = "추가 작업" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "페이지 {{page}}" placingHint = "페이지를 클릭하여 배치… (취소)" removeCommentOnly = "댓글만 제거" saveReply = "답글 저장" +searchPlaceholder = "Search comments" title = "댓글" typeComment = "댓글" typeInsertText = "텍스트 삽입" @@ -11290,6 +11325,7 @@ unsavedDesc = "저장되지 않은 변경 사항이 있습니다" closeSidebar = "레이어 사이드바 닫기" hideAll = "모든 레이어 숨기기" showAll = "모든 레이어 표시" +title = "Layers" [viewer.link] delete = "링크 삭제" @@ -11646,6 +11682,7 @@ exitRedaction = "가리기 모드 종료" exportAll = "PDF 내보내기" exportSelected = "선택한 페이지 내보내기" formFill = "양식 채우기" +hideToolbar = "Hide toolbar" multiTool = "멀티 도구" panMode = "이동 모드" print = "PDF 인쇄" @@ -11666,6 +11703,7 @@ selectAll = "모두 선택" selectByNumber = "페이지 번호로 선택" selectLanguage = "언어 선택" share = "공유" +showToolbar = "Show toolbar" toggleAnnotations = "주석 표시 전환" toggleAttachments = "첨부 파일 표시 전환" toggleBookmarks = "책갈피 표시 전환" diff --git a/frontend/editor/public/locales/ml-ML/translation.toml b/frontend/editor/public/locales/ml-ML/translation.toml index 84e43479a3..ac5f6c5967 100644 --- a/frontend/editor/public/locales/ml-ML/translation.toml +++ b/frontend/editor/public/locales/ml-ML/translation.toml @@ -1,7 +1,6 @@ alphabet = "അക്ഷരമാല" apply = "പ്രയോഗിക്കുക" applyAndContinue = "സേവ് ചെയ്ത് പുറത്ത് പോകുക" -areYouSure = "നിങ്ങൾക്ക് പോകണമെന്ന് തീർച്ചയാണോ?" back = "തിരികെ" black = "കറുപ്പ്" blue = "നീല" @@ -106,6 +105,7 @@ undoSuccess = "പ്രവർത്തനം വിജയകരമായി റ unknown = "അജ്ഞാതം" unpin = "പിൻ നീക്കം ചെയ്യുക (ടൂൾ പ്രവർത്തനത്തിന് ശേഷം മാറ്റിസ്ഥാപിക്കുക)" unsavedChanges = "നിങ്ങളുടെ PDF-ിൽ സംരക്ഷിക്കാത്ത മാറ്റങ്ങൾ ഉണ്ട്." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "സംരക്ഷിക്കാത്ത മാറ്റങ്ങൾ" unsupported = "പിന്തുണയില്ല" username = "ഉപയോക്തൃനാമം" @@ -1879,6 +1879,9 @@ width = "വീതി" [app] description = "സൗജന്യ Adobe Acrobat പകരം (10M+ ഡൗൺലോഡുകൾ)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "PDF/A-3b ആയി മാറ്റുക" convertToPdfA3bDescription = "എംബെഡുചെയ്ത അറ്റാച്ച്മെന്റുകളോടുകൂടിയ ആർക്കൈവൽ PDF സൃഷ്ടിക്കുന്നു" @@ -2933,12 +2936,16 @@ text = "ഈ അനുമതികൾ മാറ്റാൻ പറ്റാത് [chat.actions] copy = "സന്ദേശം പകർത്തുക" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling ഏജന്റ് ഓപ്ഷനുകൾ" +assistant = "AI Assistant" clearChat = "ചാറ്റ് മായ്ക്കുക" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "ഈ പ്രവർത്തനം നിങ്ങളുടെ ക് tooltip = "സ്ക്രീനിൽ നിന്ന് നിറം എടുക്കുക" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "നിറം തിരഞ്ഞെടുക്കുക" [common] +actions = "Actions" back = "തിരികെ" cancel = "റദ്ദാക്കുക" close = "അടയ്ക്കുക" +codeSample = "Code sample" collapse = "ചുരുക്കുക" confirm = "സ്ഥിരീകരിക്കുക" continue = "തുടരുക" copied = "പകർത്തി!" copy = "പകർത്തുക" done = "പൂർത്തിയായി" +enterValue = "Enter value" error = "പിശക്" expand = "വിപുലീകരിക്കുക" loading = "ലോഡുചെയ്യുന്നു..." next = "അടുത്തത്" +open = "Open" preview = "മുന്‍കാഴ്ച" previous = "മുൻപ്" refresh = "പുതുക്കുക" remaining = "ബാക്കി" retry = "വീണ്ടും ശ്രമിക്കുക" save = "സംരക്ഷിക്കുക" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "തിരഞ്ഞെടുത്തത് ക്ലിയർ ചെയ്യുക" @@ -3185,6 +3199,7 @@ title = "കംപ്രഷൻ രീതി" [compress.settings] desiredSize = "ആവശ്യമായ ഫയൽ വലുപ്പം" desiredSizePlaceholder = "വലുപ്പം നൽകുക" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "കംപ്രഷൻ ഫയൽ വലിപ്പം കുറയ്ക്കാനുള്ള എളുപ്പ മാർഗമാണ്. File Size തിരഞ്ഞെടുക്കുക, ലക്ഷ്യ വലിപ്പം നൽകുക, ഞങ്ങൾ ഗുണനിലവാരം ക്രമീകരിക്കും. Quality തിരഞ്ഞെടുക്കുക, കംപ്രഷൻ ശക്തി കൈയോടെ സജ്ജമാക്കാൻ." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF ഓപ്ഷനുകൾ" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "ആദ്യം ഒരു ഉറവിട ഫോർമാറ്റ് തിരഞ്ഞെടുക്കുക" settings = "ക്രമീകരണങ്ങൾ" single = "ഒറ്റ" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "തിരയുക..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "തിരുത്തുക" @@ -3824,11 +3861,13 @@ shareSelected = "തിരഞ്ഞെടുത്തത് പങ്കിട sharing = "പങ്കിടൽ" showAll = "എല്ലാം കാണിക്കുക" showHistory = "ചരിത്രം കാണിക്കുക" +sortBy = "Sort files" sortByDate = "തീയതി അനുസരിച്ച് ക്രമീകരിക്കുക" sortByName = "പേര് അനുസരിച്ച് ക്രമീകരിക്കുക" sortBySize = "വലുപ്പം അനുസരിച്ച് ക്രമീകരിക്കുക" storage = "സ്റ്റോറേജ്" storageState = "സംഭരണം" +storageUsed = "Storage used" synced = "സിങ്ക് ചെയ്തു" title = "PDF ഫയലുകൾ അപ്‌ലോഡ് ചെയ്യുക" toolChain = "പ്രയോഗിച്ച ടൂളുകൾ" @@ -3874,8 +3913,12 @@ addFiles = "ഫയലുകൾ ചേർക്കുക" addingFiles = "ഫയലുകൾ ചേർക്കുന്നു…" collapse = "സൈഡ്ബാർ ചുരുക്കുക" customizeGroups = "ഗ്രൂപ്പുകൾ ഇഷ്ടാനുസൃതമാക്കുക" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "തുടങ്ങാൻ ഫയലുകൾ തുറക്കുക" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "സൈഡ്ബാർ വിപുലീകരിക്കുക" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive ക്രമീകരിച്ചിട്ടില്ല" @@ -3888,15 +3931,17 @@ openFromComputer = "കമ്പ്യൂട്ടറിൽ നിന്ന് openSettings = "ക്രമീകരണങ്ങൾ തുറക്കുക" other = "മറ്റുള്ളവ" recent = "സമീപകാലം" -search = "തിരയുക" -searchPlaceholder = "ഫയലുകൾ തിരയുക..." viewAll = "എല്ലാ {{count}} ഫയലുകളും കാണുക" [fileSidebar.fileItem] closeViewer = "വ്യൂവർ അടയ്ക്കുക" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "വ്യൂവറിൽ തുറക്കുക" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "ഡിഫോൾട്ടുകളിലേക്ക് പുനഃസ subtitle = "നിങ്ങളുടെ ഫയലുകൾ പാരന്റ് വിഭാഗങ്ങളായി ഗ്രൂപ്പ് ചെയ്യുക. നിലവിലുള്ളതോ പുതിയതോ ആയ ലേബലുകൾ ഒരു വിഭാഗത്തിലേക്ക് ചേർക്കുക, അതിന്റെ പേര് മാറ്റുക, അല്ലെങ്കിൽ നിങ്ങളുടെ സ്വന്തം വിഭാഗം സൃഷ്ടിക്കുക. നിങ്ങളുടെ വിഭാഗങ്ങളിലൊന്നിലും ഇല്ലാത്ത ഫയലുകൾ “മറ്റുള്ളവ” എന്നതിന് കീഴിൽ കാണിക്കും." title = "സൈഡ്‌ബാർ വിഭാഗങ്ങൾ" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "വർക്ക്‌സ്‌പേസിലേക്ക് ചേർക്കുക" addToWorkspaceCount = "{{count}} എണ്ണം വർക്ക്‌സ്‌പേസിലേക്ക് ചേർക്കുക" @@ -3918,7 +3971,6 @@ backToMyFiles = "എന്റെ ഫയലുകളിലേക്ക് തി breadcrumbs = "ഫോൾഡർ പാത" cancel = "റദ്ദാക്കുക" classification = "വർഗ്ഗീകരണം" -clearSearch = "തിരയൽ മായ്ക്കുക" clearSelection = "തിരഞ്ഞെടുപ്പ് നീക്കുക" closeDetails = "വിശദാംശങ്ങൾ അടയ്ക്കുക" create = "സൃഷ്ടിക്കുക" @@ -3950,6 +4002,7 @@ downloadAll = "എല്ലാം ഡൗൺലോഡ് ചെയ്യുക" downloadVersion = "ഈ വേർഷൻ ഡൗൺലോഡ് ചെയ്യുക" dropOverlay = "അപ്‌ലോഡ് ചെയ്യാൻ ഫയലുകൾ ഇടുക" dropOverlaySub = "ഫയലുകൾ ആദ്യം Local-ലാണ് തുടങ്ങുന്നത്. ഫോൾഡറിലാക്കാൻ 'Move to' അല്ലെങ്കിൽ 'Save to cloud' ഉപയോഗിക്കുക." +duplicate = "Duplicate" file = "ഫയൽ" fileInfo = "File info" fileMenu = "ഫയൽ പ്രവർത്തനങ്ങൾ" @@ -3983,8 +4036,6 @@ resizeFolderTree = "ഫോൾഡർ ട്രിയുടെ വലുപ്പ save = "സംരക്ഷിക്കുക" saveToServer = "സെർവറിലേക്ക് സംരക്ഷിക്കുക" saveToServerDisabledHint = "ഈ സെർവറിൽ സെർവറിലേക്ക് സംരക്ഷിക്കൽ പ്രവർത്തനക്ഷമമാക്കിയിട്ടില്ല. അത് പ്രവർത്തനക്ഷമമാക്കാൻ അഡ്മിനെ ബന്ധപ്പെടുക." -search = "തിരയുക" -searchPlaceholder = "ഈ ഫോൾഡറിലും ഉപഫോൾഡറുകളിലും തിരയുക" selectAll = "എല്ലാം തിരഞ്ഞെടുക്കുക" selectAllHint = "എല്ലാം തിരഞ്ഞെടുക്കാൻ ക്ലിക്ക് ചെയ്യുക. ടിപ്പ്: ഓരോന്നായി ചേർക്കാൻ Ctrl (അല്ലെങ്കിൽ Cmd) അമർത്തി പിടിക്കുക, പരിധി തിരഞ്ഞെടുക്കാൻ Shift." selectedCount = "{{count}} തെരഞ്ഞെടുത്തു" @@ -4044,6 +4095,10 @@ title = "ഇനിയും ക്ലൗഡ് ഫയലുകളില്ല" hint = "അപ്‌ലോഡ് ചെയ്യാതെ സംരക്ഷിച്ച ഫയലുകൾ ഇവിടെ തന്നെ. ചേർക്കാൻ ഒരു ഫയൽ ഇടുക." title = "ലോകൽ-മാത്രമായ ഫയലുകളില്ല" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "നിങ്ങൾ തുറക്കുന്നോ എഡിറ്റ് ചെയ്യുന്നോ ചെയ്യുന്ന ഫയലുകൾ ഇവിടെ പ്രത്യക്ഷപ്പെടും." title = "ഇനിയും ഒന്നും മാറ്റിയിട്ടില്ല" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "ഫോൾഡർ ഇല്ലാതാക്കാൻ കഴിഞ്ഞില്ല." deleteFolderFailedDetail = "ഫോൾഡർ ഇല്ലാതാക്കാൻ കഴിഞ്ഞില്ല: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "ഫോൾഡർ രൂപഭാവം അപ്‌ഡേറ്റ് ചെയ്യാൻ കഴിഞ്ഞില്ല." folderAppearanceFailedDetail = "ഫോൾഡർ രൂപഭാവം അപ്‌ഡേറ്റ് ചെയ്യാൻ കഴിഞ്ഞില്ല: {{message}}" moveFilesFailed = "ഫയലുകൾ മാറ്റാൻ കഴിഞ്ഞില്ല." @@ -4113,6 +4170,11 @@ localHint = "ഈ ബ്രൗസറിൽ മാത്രം സംഭരിച shared = "പങ്കിട്ടത്" sharedHint = "ലിങ്ക് വഴി നിങ്ങളോടൊപ്പം പങ്കിട്ടത്" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "പഴയത് ആദ്യം" modifiedDesc = "പുതിയത് ആദ്യം" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "സ്റ്റോറേജിൽ ഫയലുകളി open = "തുറക്കുക" openFile = "ഫയൽ തുറക്കുക" openFiles = "ഫയലുകൾ തുറക്കുക" +selectFile = "Select {{name}}" selectFromStorage = "സ്റ്റോറേജിൽ നിന്ന് തിരഞ്ഞെടുക്കുക" upload = "അപ്‌ലോഡ്" uploadFile = "ഫയൽ അപ്‌ലോഡ് ചെയ്യുക" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "എല്ലാം സേവ് ചെയ്തു" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV extract ചെയ്യാനായില്ല" extractXlsxError = "XLSX extract ചെയ്യാനായില്ല" +filled = "filled" flattenAfterFilling = "പൂരിപ്പിച്ചതിന് ശേഷം ഫ്ലാറ്റൻ ചെയ്യുക" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "ആവ." requiredFieldsError = "ആവശ്യമായ എല്ലാ fields-ഉം പൂരിപ്പിക്കുക" rescanFields = "ഫീൽഡുകൾ വീണ്ടും സ്കാൻ ചെയ്യുക" @@ -4795,9 +4863,6 @@ title = "ചിത്രം PDF-ലേക്ക്" [imageToPdf] tags = "പരിവർത്തനം,img,jpg,ചിത്രം,ഫോട്ടോ" -[infoBanner] -dismiss = "അടയ്ക്കുക" - [invite] acceptError = "അക്കൗണ്ട് സൃഷ്ടിക്കൽ പരാജയപ്പെട്ടു" accountFor = "അക്കൗണ്ട് സൃഷ്ടിക്കുന്നത്" @@ -4979,6 +5044,34 @@ uploadSuccess = "അപ്‌ലോഡ് വിജയകരം!" uploadSuccessMessage = "നിങ്ങളുടെ ചിത്രങ്ങൾ മാറ്റി." validating = "സെഷൻ ശരിവെയ്ക്കുന്നു..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "സ്കാൻ ചെയ്ത് ചിത്രങ്ങൾ അപ്‌ലോഡ് ചെയ്യുക. ചിത്രങ്ങൾ സ്വയമേവ PDF-യായി മാറും." descriptionNoConvert = "നിങ്ങളുടെ മൊബൈൽ ഉപകരണത്തിൽ നിന്ന് ചിത്രങ്ങൾ അപ്‌ലോഡ് ചെയ്യാൻ സ്കാൻ ചെയ്യുക." @@ -4997,8 +5090,13 @@ title = "മൊബൈലിൽ നിന്ന് അപ്‌ലോഡ് ച tags = "മൾട്ടി ടൂൾ,മൾട്ടി ഓപ്പറേഷൻ,യുഐ,ക്ലിക്ക് ഡ്രാഗ്,ഫ്രണ്ട് എൻഡ്,ക്ലയിന്റ് സൈഡ്,ഇന്ററാക്ടീവ്,അനങ്ങാത്ത,നീക്കുക,ഇല്ലാതാക്കുക,കൈമാറുക,വിഭജിക്കുക" title = "PDF മൾട്ടി ടൂൾ" -[navbar] -search = "തിരയുക" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "ഓതന്റിക്കേഷൻ വിജയിച്ചില്ല. ഈ വിൻഡോ അടച്ച് വീണ്ടും ശ്രമിക്കാം." @@ -5127,6 +5225,7 @@ activeFiles = "ആക്ടീവ് ഫയലുകൾ കാ allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "അടയ്ക്കുക" cropSettings = "ഇപ്പോൾ നമുക്ക് ക്രോപ്പ് ചെയ്യാൻ വേണ്ട ഫയൽ തെരഞ്ഞെടുത്തതിനാൽ, PDF ക്രോപ്പ് ചെയ്യേണ്ട പ്രദേശം തീരുമാനിക്കാൻ ക്രോപ്പ് ടൂൾ ക്രമീകരിക്കാം." +dialogLabel = "Onboarding" fileCheckbox = "ഫയലുകളിൽ ഒന്നിൽ ക്ലിക്കുചെയ്താൽ അത് പ്രോസസിംഗിനായി തിരഞ്ഞെടുക്കപ്പെടും. ബാച്ച് പ്രവർത്തനങ്ങൾക്ക് നിങ്ങൾക്ക് ഒന്നിലധികം ഫയലുകൾ തിരഞ്ഞെടുക്കാം." fileReplacement = "മാറ്റം വരുത്തിയ ഫയൽ വർക്ക്ബെഞ്ചിലെ യഥാർത്ഥ ഫയലിനെ സ്വമേധയായി മാറ്റിസ്ഥാപിക്കും, ഇതിലൂടെ കൂടുതൽ ടൂളുകൾ പ്രവർത്തിപ്പിക്കുന്നത് എളുപ്പമാകും." filesButton = "ക്വിക് ആക്സസ് ബാറിലെ ഫയലുകൾ ബട്ടൺ ഉപയോഗിച്ച് നിങ്ങൾക്ക് ടൂളുകളിൽ ഉപയോഗിക്കാൻ PDFകൾ അപ്‌ലോഡ് ചെയ്യാം." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF PDF/A-ലേക്ക്" tags = "ആർക്കൈവ്,ദീർഘകാല,മാനദണ്ഡം,പരിവർത്തനം,സംഭരണം,സംരക്ഷണം" title = "PDF PDF/A-ലേക്ക്" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "പ്രിന്റ്,സ്റ്റാൻഡേർഡ്,കൺവർഷൻ,പ്രൊഡക്ഷൻ,പ്രീപ്രസ്,ആർക്കൈവ്" title = "PDF-നെ PDF/X-ലേക്ക്" @@ -6045,6 +6151,7 @@ small = "500 ക്രെഡിറ്റുകൾ" xsmall = "100 ക്രെഡിറ്റുകൾ" [plan.availablePlans] +currency = "Billing currency" subtitle = "നിങ്ങളുടെ ആവശ്യങ്ങൾക്ക് അനുയോജ്യമായ പ്ലാൻ തിരഞ്ഞെടുക്കുക" title = "ലഭ്യമായ പ്ലാനുകൾ" @@ -6624,6 +6731,7 @@ reachedTitle = "Monthly spend limit എത്തി" title = "Stripe portal തുറക്കാനായില്ല" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "{{allowance}} free PDFs-ൽ ഉപയോഗിച്ചത്" capSuffix_other = "{{allowance}} free PDFs-ൽ ഉപയോഗിച്ചത്" eyebrow = "Processor trial" @@ -6873,6 +6981,32 @@ label = "Jira" description = "ഒരു policy run ചെയ്യുമ്പോൾ document അല്ലെങ്കിൽ notification email ചെയ്യുക." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "processed documents Nextcloud-ലേക്ക് file ചെയ്യുക." @@ -7077,11 +7211,6 @@ title = "Official SDKs" beta = "Beta" deprecated = "Deprecated" -[portal.docs.search] -empty = "പൊരുത്തപ്പെടുന്ന docs ഇല്ല" -placeholder = "docs search ചെയ്യുക" -results = "{{count}} ഫലങ്ങൾ" - [portal.docs.skills] eyebrow = "SKILLS" lead = "നിങ്ങളുടെ agent ഒരൊറ്റ tool ആയി invoke ചെയ്യുന്ന bundled, named capabilities. ഓരോ skill-വും evals attached ആയ deterministic op chain ആണ്." @@ -7180,11 +7309,11 @@ editorAction = "എഡിറ്റർ" empty = "ഈ ഫിൽട്ടറുമായി പൊരുത്തപ്പെടുന്ന ഡോക്യുമെന്റുകളില്ല." rowActions = "വരി പ്രവർത്തനങ്ങൾ" sensitiveLabel = "സെൻസിറ്റീവ്" -sensitiveTitle = "സെൻസിറ്റീവ് — പ്രവേശനം ആവശ്യമാണ്" [portal.documents.table.columns] action = "പൈപ്പ്‌ലൈൻ / പ്രവർത്തനം" document = "ഡോക്യുമെന്റ്" +labels = "Labels" product = "ഉൽപ്പന്നം" status = "സ്ഥിതി" time = "സമയം" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "അവസാനം കണ്ടത്" region = "Region" status = "സ്ഥിതി" +target = "Target" version = "Version" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "ഈ view-ൽ അപ്രതീക്ഷിത പിശക് retry = "വീണ്ടും ശ്രമിക്കുക" title = "ഈ പേജിൽ എന്തോ പിഴച്ചു" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Back" body = "desktop app download ചെയ്യുക അല്ലെങ്കിൽ server self-host ചെയ്യുക." @@ -7380,11 +7560,6 @@ cancel = "റദ്ദാക്കുക" confirm = "key revoke ചെയ്യുക" title = "API key revoke ചെയ്യുക" -[portal.infrastructure.attestationLabel] -attested = "Attested" -inScope = "പരിധിയിൽ" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Category പ്രകാരം audit events ഫിൽട്ടർ ചെയ്യുക" heading = "Audit logs" @@ -7456,11 +7631,6 @@ info = "വിവരം" success = "വിജയം" warning = "മുന്നറിയിപ്പ്" -[portal.infrastructure.certLabel] -certified = "Certified" -inProgress = "പുരോഗമിക്കുന്നു" -notStarted = "ആരംഭിച്ചിട്ടില്ല" - [portal.infrastructure.createKey] cancel = "റദ്ദാക്കുക" createKey = "Key സൃഷ്ടിക്കുക" @@ -7474,238 +7644,10 @@ subtitleCreated = "ഈ secret ഇപ്പോൾ copy ചെയ്യുക — title = "API key സൃഷ്ടിക്കുക" titleCreated = "Key സൃഷ്ടിച്ചു" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "Queued" -rolledBack = "Rolled back" -rolling = "Rolling out" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "വിന്യസിച്ചത്" -environment = "Environment" -product = "ഉൽപ്പന്നം" -status = "സ്ഥിതി" -version = "Version" -when = "എപ്പോൾ" - -[portal.infrastructure.deployments.recent] -heading = "സമീപകാല deployments" -subheading = "ഉൽപ്പന്നങ്ങളിലെയും environments-കളിലെയും ഏറ്റവും പുതിയ rollouts." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instances" -latency = "Latency" -load = "Load" -p99 = "P99" -region = "Region" -status = "സ്ഥിതി" -throughput = "Throughput" -uptime = "Uptime" -version = "Version" - -[portal.infrastructure.deployments.regions] -heading = "Regions" -subheading = "വിന്യസിച്ച ഓരോ Stirling region-ന്റെയും live health — latency, load, rollout version." - -[portal.infrastructure.deployments.regions.empty] -description = "നിങ്ങളുടെ workspace provision ചെയ്തുകഴിഞ്ഞാൽ deployed regions ഇവിടെ ദൃശ്യമാകും." -title = "Regions വിന്യസിച്ചിട്ടില്ല" - [portal.infrastructure.keyLabel] active = "Active" revoked = "Revoked" -[portal.infrastructure.modelLabel] -active = "Active" -degraded = "Degraded" -disabled = "Disabled" - -[portal.infrastructure.models] -heading = "Models" -msValue = "{{value}} ms" -subheading = "നിങ്ങളുടെ workspace-ിലുടനീളം document processing പ്രവർത്തിപ്പിക്കുന്ന model catalogue, routing എന്നിവ." - -[portal.infrastructure.models.byom] -description = "Data-residency-bound processing-നായി on-prem അല്ലെങ്കിൽ self-hosted model register ചെയ്ത് ഒരു region-ലേക്ക് pin ചെയ്യുക." -title = "നിങ്ങളുടെ സ്വന്തം model കൊണ്ടുവരിക" - -[portal.infrastructure.models.catalogue] -heading = "Catalogue" -sub = "Live latency, cost എന്നിവയോടെ നിങ്ങളുടെ workspace-ന് ലഭ്യമായ managed models." -subEnterprise = "Managed, bring-your-own, on-prem models — per-region pinning ലഭ്യമാണ്." - -[portal.infrastructure.models.catalogue.empty] -description = "നിങ്ങളുടെ workspace-ന്റെ catalogue-ിലുള്ള models ഇവിടെ ദൃശ്യമാകും." -title = "Models ലഭ്യമല്ല" - -[portal.infrastructure.models.columns] -cost = "ചെലവ്" -latency = "Latency" -load = "Load" -model = "Model" -status = "സ്ഥിതി" -type = "തരം" -version = "Version" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/call" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "സജീവ models" -avgLatency = "ശരാശരി latency" -included = "ഉൾപ്പെടുത്തി" -monthlySpend = "മാസിക model ചെലവ്" - -[portal.infrastructure.models.routing] -empty = "Routing rules configured ചെയ്തിട്ടില്ല." -heading = "Routing rules" -sub = "ഓരോ operation-യും ഏത് model കൈകാര്യം ചെയ്യുന്നു. കൂടുതൽ narrow rule ഒന്നും match ചെയ്യാത്തപ്പോൾ default ബാധകമാണ്." -subLocked = "Operations പ്രത്യേക models-ലേക്ക് route ചെയ്യുക — paid plans-ൽ ലഭ്യമാണ്." - -[portal.infrastructure.models.routing.lockedBanner] -description = "ഓരോ operation, document type എന്നിവ ഏത് model കൈകാര്യം ചെയ്യണമെന്ന് നിയന്ത്രിക്കാൻ Pro-യിലേക്ക് upgrade ചെയ്യുക." -title = "Model routing ഒരു paid feature ആണ്" - -[portal.infrastructure.models.routingColumns] -default = "Default" -docType = "ഡോക്യുമെന്റ് തരം" -modelForAria = "{{operation}}-ക്കുള്ള model" -operation = "Operation" -routedTo = "Route ചെയ്തത്" - -[portal.infrastructure.modelTypeLabel] -classification = "വർഗ്ഗീകരണം" -extraction = "Extraction" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degraded" -down = "Down" -healthy = "Healthy" - -[portal.infrastructure.security.access.byok] -description = "നിങ്ങളുടെ സ്വന്തം KMS-ൽ നിന്ന് key നൽകുക. Stirling അത് ഉപയോഗിച്ച് encrypt ചെയ്യുന്നു, പക്ഷേ ഇപ്പോഴും വായിക്കാം." -label = "നിങ്ങളുടെ സ്വന്തം key കൊണ്ടുവരിക (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Keys ഒരിക്കലും നിങ്ങളുടെ KMS വിട്ടുപോകില്ല. Stirling ciphertext മാത്രം കൈവശം വയ്ക്കുന്നു." -label = "നിങ്ങളുടെ സ്വന്തം key കൈവശം വയ്ക്കുക (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling encryption keys കൈകാര്യം ചെയ്യുന്നു. ഏറ്റവും ലളിതം — നിങ്ങളുടെ ഭാഗത്ത് key ops ഒന്നുമില്ല." -label = "Stirling-held keys" - -[portal.infrastructure.security.accessPolicy] -heading = "ഡോക്യുമെന്റ് access policy" -subheading = "At rest ഉള്ള processed documents ആർക്കെല്ലാം decrypt ചെയ്യാനാകുമെന്ന് നിയന്ത്രിക്കുന്നു." - -[portal.infrastructure.security.attestations] -heading = "Compliance attestations" -noReport = "Report ലഭ്യമല്ല" -subheading = "Attested controls-ൽ reports ലഭ്യമായ framework-by-framework audit posture." -viewReport = "Report കാണുക →" - -[portal.infrastructure.security.compliance] -heading = "Compliance" -subheading = "Stirling platform-നെ ഉൾക്കൊള്ളുന്ന attestations, certifications." - -[portal.infrastructure.security.empty] -description = "നിങ്ങളുടെ workspace-ന്റെ security configuration ഇവിടെ ദൃശ്യമാകും." -title = "Security posture ലഭ്യമല്ല" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK ഉപയോഗിക്കുമ്പോൾ encryption keys നിങ്ങളുടെ KMS വിട്ടുപോകില്ല. Stirling നിങ്ങൾക്ക് ഏത് സമയത്തും revoke ചെയ്യാനാകുന്ന ciphertext മാത്രം store, process ചെയ്യുന്നു." -title = "Stirling-ന് നിങ്ങളുടെ ഡോക്യുമെന്റുകൾ decrypt ചെയ്യാനാവില്ല" - -[portal.infrastructure.security.ipAllowlist] -empty = "IP ranges configured ചെയ്തിട്ടില്ല — എല്ലാ IPs-ഉം അനുവദിച്ചിരിക്കുന്നു." -heading = "IP allowlist" -sub = "API access ഈ CIDR ranges-ലേക്ക് പരിമിതപ്പെടുത്തിയിരിക്കുന്നു." -subLocked = "പരിചിതമായ IP ranges-ലേക്ക് API access പരിമിതപ്പെടുത്തുക — paid plans-ൽ ലഭ്യമാണ്." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "നിർദ്ദിഷ്ട networks-ലേക്ക് API access പരിമിതപ്പെടുത്താൻ Pro-യിലേക്ക് upgrade ചെയ്യുക." -title = "IP allowlisting ഒരു paid feature ആണ്" - -[portal.infrastructure.security.ipColumns] -added = "ചേർത്തത്" -addedBy = "ചേർത്തത്" -cidr = "CIDR" -label = "Label" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "Encryption key management" -keyId = "Key identifier" -lastRotated = "അവസാനം rotated" -rotateKey = "Key rotate ചെയ്യുക" -rotationPolicy = "Rotation policy" -subheading = "At rest ഉള്ള documents encrypt ചെയ്യുന്ന keys-ന്റെ custody — ആർക്കെല്ലാം decrypt ചെയ്യാം, keys എങ്ങനെ rotate ചെയ്യും." - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key (BYOK), hold-your-own-key (HYOK) custody എന്നിവ Enterprise-ൽ ലഭ്യമാണ്. നിങ്ങളുടെ സ്വന്തം KMS-ൽ നിന്ന് keys നൽകാൻ upgrade ചെയ്യുക." -title = "നിങ്ങളുടെ plan-ൽ keys Stirling കൈകാര്യം ചെയ്യുന്നു" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia Pacific" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR data boundary" -label = "European Union" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "United States" - -[portal.infrastructure.security.residencyHeader] -heading = "Data residency" -subheading = "ഡോക്യുമെന്റുകൾ എവിടെ store, process ചെയ്യുന്നു." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} ഉപയോഗിച്ചു" - -[portal.infrastructure.storage.empty] -description = "Connected storage, usage എന്നിവ ഇവിടെ ദൃശ്യമാകും." -title = "Storage configured ചെയ്തിട്ടില്ല" - -[portal.infrastructure.storage.lifecycle] -active = "സജീവം" -activeRange = "0–{{value}}d" -archived = "Archived" -coldStorage = "cold storage" -deleted = "Deleted" -never = "ഒരിക്കലും" -purged = "purged" - -[portal.infrastructure.storage.providers] -connect = "Connect" -connected = "Connected" -heading = "Connected providers" -subheading = "Processed artifacts എഴുതുന്ന ഇടം." - -[portal.infrastructure.storage.retention] -heading = "Retention" -subheading = "Lifecycle deletion-ന് മുമ്പ് artifacts എത്രകാലം സൂക്ഷിക്കും." -windowLabel = "Default retention window" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} ദിവസം" -days_other = "{{count}} ദിവസം" -never = "ഒരിക്കലും delete ചെയ്യരുത്" - -[portal.infrastructure.storage.totalUsage] -heading = "ആകെ ഉപയോഗം" -progressLabel = "ഉപയോഗിച്ച storage" -subheading = "Connected providers മുഴുവൻ ഉപയോഗിച്ച storage." - [portal.infrastructure.tabs] apiKeys = "API Keys" audit = "Audit Logs" @@ -7715,11 +7657,9 @@ security = "Security" storage = "Storage" [portal.integrations] -addAnother = "മറ്റൊന്ന് ചേർക്കുക" availableHeading = "ലഭ്യമാണ്" comingSoonHeading = "ഉടൻ വരുന്നു" connect = "ബന്ധിപ്പിക്കുക" -connectedHeading = "ബന്ധിപ്പിച്ചു" connectionCount_one = "{{count}} കണക്ഷൻ" connectionCount_other = "{{count}} കണക്ഷനുകൾ" customApi = "Custom API" @@ -7737,6 +7677,10 @@ security = "Security" signing = "Signing" storage = "Storage" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "ബന്ധിപ്പിച്ചു" @@ -7767,7 +7711,6 @@ integrations = "Integrations" pipelines = "പൈപ്പ്‌ലൈനുകൾ" policies = "നയങ്ങൾ" procurement = "Procurement" -settings = "Settings" sources = "ഉറവിടങ്ങൾ" usage = "Usage & Billing" users = "Users" @@ -7784,7 +7727,7 @@ title = "പൈപ്പ്‌ലൈനുകൾ" newPipeline = "പുതിയ പൈപ്പ്‌ലൈൻ" [portal.pipelines.builder] -addStep = "Tool ചേർക്കുക" +activate = "Activate" back = "പൈപ്പ്‌ലൈനുകളിലേക്ക് മടങ്ങുക" cannotFollow = "{{produced}} സ്വീകരിക്കാൻ കഴിയില്ല" chooseAccount = "ഒരു account തിരഞ്ഞെടുക്കുക" @@ -7792,28 +7735,38 @@ chooseDestination = "ഒരു destination തിരഞ്ഞെടുക്ക chooseOperation = "ഈ step ചെയ്യേണ്ടത് തിരഞ്ഞെടുക്കുക" chooseSource = "ഒരു source തിരഞ്ഞെടുക്കുക" discard = "മാറ്റങ്ങൾ ഉപേക്ഷിക്കുക" -enabled = "Enabled" inputs = "Input" inputSource = "Input source" inputTrigger = "Trigger" keepEditing = "Editing തുടരുക" +moreActions = "More actions" needsConfiguring = "സജ്ജീകരണം ആവശ്യമാണ്" -needsUpload = "Uploaded file ആവശ്യമാണ്" -noSources = "ഇതുവരെ sources ഒന്നും ബന്ധിപ്പിച്ചിട്ടില്ല. Input ആയി ഉപയോഗിക്കാൻ ഒന്ന് സൃഷ്ടിക്കുക." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "നിങ്ങളുടെ search-നോട് പൊരുത്തപ്പെടുന്ന tools ഇല്ല." -pipelineSettings = "പൈപ്പ്‌ലൈൻ settings" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Tools search ചെയ്യുക" -selectToolBody = "നിങ്ങളുടെ പൈപ്പ്‌ലൈൻ നിർമ്മിക്കാൻ ഒരു tool ചേർക്കുക." -selectToolTitle = "ഇതുവരെ tools ഇല്ല" sendToSystem = "മറ്റൊരു system-ലേക്ക് അയയ്ക്കുക" stepsIncompatible = "മുമ്പത്തെ step സൃഷ്ടിക്കുന്നതിൽ ഈ steps പ്രവർത്തിക്കാൻ കഴിയില്ല: {{tools}}." stepsNeedSetup = "save ചെയ്യുന്നതിന് മുമ്പ് ഈ steps-ന് ഇനിയും സജ്ജീകരണം ആവശ്യമാണ്: {{tools}}." -toolSettings = "Tool settings" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "തിരിച്ചറിയാത്ത operation, അതേപടി സൂക്ഷിച്ചു." unsavedBody = "നിങ്ങൾക്ക് save ചെയ്യാത്ത മാറ്റങ്ങളുണ്ട്. പോകുന്നതിനുമുമ്പ് അവ save ചെയ്യണോ, അല്ലെങ്കിൽ ഉപേക്ഷിക്കണോ?" unsavedTitle = "Save ചെയ്യാത്ത മാറ്റങ്ങൾ" -uploadUnsupported = "Uploaded files ഇതുവരെ pipelines-ൽ support ചെയ്യുന്നില്ല, അതിനാൽ ഈ steps save ചെയ്യാൻ കഴിയില്ല: {{tools}}." usesDefaults = "Default settings ഉപയോഗിച്ച് run ചെയ്യുന്നു" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "മുമ്പത്തെ step-ൽ നിന്നുള്ള ഓരോ file-ഉം സംയോജിപ്പിക്കുന്നു" @@ -7825,31 +7778,36 @@ undeclared-operation = "ഈ step എന്താണ് സ്വീകരിക [portal.pipelines.composer] addTool = "Tool ചേർക്കുക" -cancel = "റദ്ദാക്കുക" -chainEmpty = "നിങ്ങളുടെ പൈപ്പ്‌ലൈൻ നിർമ്മിക്കാൻ ഒരു tool ചേർക്കുക." create = "പൈപ്പ്‌ലൈൻ സൃഷ്ടിക്കുക" +createPaused = "Create paused" editingUnsupported = "Editing-നായി ഈ tool params പ്രദർശിപ്പിക്കുന്നത് ഇതുവരെ support ചെയ്യുന്നില്ല." -moveDown = "താഴേക്ക് നീക്കുക" -moveUp = "മുകളിലേക്ക് നീക്കുക" +editSource = "Edit source" name = "പേര്" namePlaceholder = "ഉദാ. Redaction sweep" noToolSettings = "ഈ tool-ന് configurable settings ഇല്ല." -operations_one = "Operation ({{count}})" -operations_other = "Operations ({{count}})" output = "Output" -removeStep = "Operation നീക്കം ചെയ്യുക" save = "മാറ്റങ്ങൾ save ചെയ്യുക" scheduleEvery = "ഓരോ" -sources = "ഉറവിടങ്ങൾ" -sourcesLoading = "Sources load ചെയ്യുന്നു..." trigger = "Trigger" triggerManual = "Manual മാത്രം" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "ദിവസങ്ങൾ" hours = "മണിക്കൂറുകൾ" minutes = "മിനിറ്റുകൾ" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\" delete ചെയ്യണോ? ഇത് പഴയപടി ആക്കാൻ കഴിയില്ല." cancel = "റദ്ദാക്കുക" @@ -7867,6 +7825,37 @@ connectSource = "ഒരു source ബന്ധിപ്പിക്കുക" description = "നിങ്ങളുടെ ആദ്യ പൈപ്പ്‌ലൈൻ സൃഷ്ടിക്കുക: അത് run ചെയ്യുന്ന sources തിരഞ്ഞെടുക്കുക, operations chain ചെയ്യുക, output എവിടേക്ക് പോകണമെന്ന് തിരഞ്ഞെടുക്കുക." title = "ഇതുവരെ പൈപ്പ്‌ലൈനുകളില്ല" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "സജീവം" paused = "Paused" @@ -7879,7 +7868,6 @@ completed_one = "Run പൂർത്തിയായി." completed_other = "എല്ലാ {{count}} runs പൂർത്തിയായി." empty = "Run ചെയ്യാൻ ഒന്നുമില്ല: sources-ൽ process ചെയ്യാൻ documents ഇല്ലായിരുന്നു." failed = "Run പരാജയപ്പെട്ടു: {{error}}" -historyCleared = "history മായ്ച്ചു. അടുത്ത run sources-ൽ ഇപ്പോഴുള്ള എല്ലാം വീണ്ടും process ചെയ്യും." inFlight = "പ്രവർത്തിപ്പിക്കാൻ പുതിയത് ഒന്നുമില്ല: മുമ്പത്തെ run-ൽ നിന്നുള്ള ഡോക്യുമെന്റുകൾ ഇപ്പോഴും process ചെയ്യുന്നു." parked_one = "പ്രവർത്തിപ്പിക്കാൻ ഒന്നുമില്ല: {{count}} ഡോക്യുമെന്റ് മുമ്പ് പരാജയപ്പെട്ടു, parked ആണ്. കാരണം പരിഹരിക്കുക, തുടർന്ന് വീണ്ടും ശ്രമിക്കാൻ history മായ്ക്കുക." parked_other = "പ്രവർത്തിപ്പിക്കാൻ ഒന്നുമില്ല: {{count}} ഡോക്യുമെന്റുകൾ മുമ്പ് പരാജയപ്പെട്ടു, parked ആണ്. കാരണം പരിഹരിക്കുക, തുടർന്ന് വീണ്ടും ശ്രമിക്കാൻ history മായ്ക്കുക." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "ഉറവിടങ്ങൾ" status = "സ്ഥിതി" steps = "Steps" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Folder watch" @@ -8209,6 +8198,24 @@ label = "Jira issue-ലേക്ക് attach ചെയ്യുക" description = "processed ഡോക്യുമെന്റ് attachment ആയി അയയ്ക്കുന്നു." label = "ഡോക്യുമെന്റ് email ചെയ്യുക (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "processed ഡോക്യുമെന്റ് ഒരു folder-ലേക്ക് എഴുതുന്നു." label = "Nextcloud-ലേക്ക് upload ചെയ്യുക" @@ -8683,16 +8690,6 @@ maxed = "പരമാവധി എത്തി" subtitle = "നിങ്ങളുടെ free trial {{date}} വരെ പ്രവർത്തിക്കും. കാർഡ് ആവശ്യമില്ല." title = "Enterprise trial" -[portal.search] -ariaLabel = "തിരയുക" -placeholder = "Stirling തിരയുക — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Quick actions ലഭ്യമാകുമ്പോൾ ഇവിടെ പ്രത്യക്ഷപ്പെടും." -noActionsTitle = "Quick actions ഇല്ല" -noMatches = "\"{{query}}\" എന്നതിനൊന്നും പൊരുത്തങ്ങളില്ല" -noMatchesDescription = "മറ്റൊരു keyword ശ്രമിക്കുക അല്ലെങ്കിൽ catalogue browse ചെയ്യുക." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "റദ്ദാക്കുക" confirm = "ഇല്ലാതാക്കുക" title = "ഉറവിടം ഇല്ലാതാക്കണോ?" -[portal.sources.empty] -description = "നിങ്ങളുടെ നയങ്ങൾക്ക് ഡോക്യുമെന്റുകൾ എടുക്കാൻ ഇടമുണ്ടാകാൻ ഒരു folder (ഉടൻ cloud storage-ും) കണക്റ്റ് ചെയ്യുക." -title = "ഇതുവരെ ഉറവിടങ്ങൾ കണക്റ്റ് ചെയ്തിട്ടില്ല" - [portal.sources.kpi] inUse = "ഉപയോഗത്തിലാണ്" total = "Connections" @@ -8785,6 +8778,7 @@ unused = "ഉപയോഗിക്കാത്തത്" documents = "Documents" source = "ഉറവിടം" status = "സ്ഥിതി" +type = "Type" usedBy = "നയങ്ങൾ" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "നയങ്ങളും സ്വകാര്യതയും" [settings.preferences] title = "അഭിരുചികൾ" -[settings.search] -placeholder = "സജ്ജീകരണ പേജുകൾ തിരയുക..." - [settings.security] description = "അക്കൗണ്ട് സുരക്ഷിതമായി നിലനിർത്താൻ പാസ്‌വേഡ് അപ്‌ഡേറ്റ് ചെയ്യുക." title = "സുരക്ഷ" @@ -10128,6 +10119,7 @@ last = "അവസാന പേജ്" maintainRatio = "വീക്ഷണാനുപാതം നിലനിർത്തുക ടോഗിൾ ചെയ്യുക" next = "അടുത്ത പേജ്" noSavedSigs = "സംരക്ഷിച്ച ഒപ്പുകളൊന്നും കണ്ടെത്തിയില്ല" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "വ്യക്തിഗത ഒപ്പുകൾ" previous = "മുമ്പത്തെ പേജ്" redo = "വീണ്ടും ചെയ്യുക" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "ബാക്ക്ഗ്രൗണ്ട് ന hint = "നിങ്ങളുടെ ഒപ്പിന്റെ PNG അല്ലെങ്കിൽ JPG ചിത്രം അപ്‌ലോഡ് ചെയ്യുക" label = "ഒപ്പിന്റെ ചിത്രം അപ്‌ലോഡ് ചെയ്യുക" placeholder = "ഇമേജ് ഫയൽ തിരഞ്ഞെടുക്കുക" +previewAlt = "Current image signature" processing = "ഇമേജ് പ്രോസസ്സ് ചെയ്യുന്നു..." removeBackground = "വെള്ള ബാക്ക്ഗ്രൗണ്ട് നീക്കംചെയ്യുക (ട്രാൻസ്‌പാരന്റ് ആക്കുക)" @@ -10179,6 +10172,17 @@ saved = "മുകളിൽ നിന്ന് ഒരു സേവ് ചെയ text = "മുകളിൽ നിങ്ങളുടെ പേര് നൽകിയ ശേഷം, PDF-ിലെ ഏതെങ്കിലും സ്ഥലത്ത് ക്ലിക്ക് ചെയ്ത് ഒപ്പ് സ്ഥാപിക്കുക." title = "ഒപ്പ് എങ്ങനെ ചേർക്കാം" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "ഒപ്പ് നീക്കുക" pause = "സ്ഥാപിക്കൽ നിർത്തുക" @@ -10625,6 +10629,23 @@ title = "സെർവറിലേക്ക് അപ്‌ലോഡ് ചെയ updateButton = "സെർവറിൽ അപ്‌ഡേറ്റ് ചെയ്യുക" uploadButton = "സെർവറിലേക്ക് അപ്‌ലോഡ് ചെയ്യുക" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "തിരികെ പോകുക" pdfTools = "PDF ടൂളുകൾ" placeholder = "തുടങ്ങാൻ ഒരു ടൂൾ തിരഞ്ഞെടുക്കുക" premiumFeature = "പ്രീമിയം ഫീച്ചർ:" -searchTools = "ടൂളുകൾ തിരയുക" toolsHeader = "ടൂളുകൾ" viewAllTools = "എല്ലാ ടൂളുകളും കാണുക" @@ -10822,6 +10842,9 @@ removal = "നീക്കം" signing = "ഒപ്പിടൽ" verification = "സ്ഥിരീകരണം" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "ഒരു ടൂളും കണ്ടെത്തിയില്ല" noTools = "ടൂളുകൾ ലഭ്യമല്ല" @@ -10979,6 +11002,13 @@ approver = "നയം അംഗീകരിക്കുന്നു" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "{{email}}-ലേക്കുള്ള ക്ഷണം റദ്ദാക്കണോ? നിലവിലെ ലിങ്ക് ഉപയോഗിച്ച് അവർക്ക് ചേരാൻ കഴിയില്ല." cancelInviteTitle = "ക്ഷണം റദ്ദാക്കുക" @@ -10998,10 +11028,8 @@ title = "ഇതുവരെ members ഇല്ല" addToTeam = "Team-ലേക്ക് ചേർക്കുക" guestCount = "{{count}} guest" guests = "Guests" -guestsDesc = "External collaborators, നിങ്ങൾ പങ്കിട്ടതിലേക്കു മാത്രം പരിമിതം. Editor മാത്രം." ledBy = "{{owner}} നയിക്കുന്നു" org = "Organization" -orgDesc = "Org-wide authority-യും policy approval-ഉം ഉള്ള owners" owners = "{{count}} owner" team = "{{name}} team" teamMeta = "{{count}} ആളുകൾ" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "ക്ഷണിച്ചത് {{who}}" cancel = "റദ്ദാക്കുക" -count = "{{count}} തീർപ്പാകാത്തത്" -desc = "ഇതുവരെ ചേർന്നിട്ടില്ലാത്ത ക്ഷണിക്കപ്പെട്ടവർ. അവർ അംഗീകരിക്കുന്നതുവരെ അവർ ഒരു സീറ്റ് കൈവശം വയ്ക്കും." expiresInDays_one = "{{count}} ദിവസത്തിനകം കാലഹരണപ്പെടും" expiresInDays_other = "{{count}} ദിവസത്തിനകം കാലഹരണപ്പെടും" expiresToday = "ഇന്ന് കാലഹരണപ്പെടും" title = "തീർപ്പാകാത്ത ക്ഷണങ്ങൾ" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "ബാക്കെൻഡുമായി ബന്ധപ്പെടുന്നതിൽ എന്തോ പിശക് സംഭവിച്ചു, അല്ലെങ്കിൽ നിങ്ങൾക്ക് ആക്സസ് ഇല്ല. വീണ്ടും ശ്രമിക്കുക." title = "അംഗങ്ങളെ ലോഡ് ചെയ്യാനായില്ല" @@ -11237,12 +11267,15 @@ searchPlaceholder = "അറ്റാച്ച്മെന്റുകൾ തി title = "അറ്റാച്ച്മെന്റുകൾ" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "ബുക്ക്‌മാർക്ക് ശീർഷകം" bookmarkTitleRequired = "ബുക്ക്‌മാർക്ക് ശീർഷകം ആവശ്യമാണ്" closeSidebar = "ബുക്ക്‌മാർക്കുകളുടെ സൈഡ്‌ബാർ അടയ്ക്കുക" collapseAll = "എല്ലാ ബുക്ക്‌മാർക്കുകളും ചുരുക്കുക" +empty = "No bookmarks in this document" expandAll = "എല്ലാ ബുക്ക്‌മാർക്കുകളും വിപുലീകരിക്കുക" searchPlaceholder = "ബുക്ക്‌മാർക്കുകൾ തിരയുക" +title = "Bookmarks" [viewer.comments] addComment = "കമന്റ് ചേർക്കുക" @@ -11264,10 +11297,12 @@ locateAnnotation = "ഡോക്യുമെന്റിൽ കണ്ടെത moreActions = "കൂടുതൽ പ്രവർത്തികൾ" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "പേജ് {{page}}" placingHint = "ഇടാൻ ഒരു പേജിൽ ക്ലിക്ക് ചെയ്യുക… (റദ്ദാക്കുക)" removeCommentOnly = "കമന്റ് മാത്രം നീക്കുക" saveReply = "മറുപടി സംരക്ഷിക്കുക" +searchPlaceholder = "Search comments" title = "കമന്റുകൾ" typeComment = "കമന്റ്" typeInsertText = "ടെക്സ്റ്റ് ചേർക്കുക" @@ -11290,6 +11325,7 @@ unsavedDesc = "നിങ്ങൾക്ക് സംരക്ഷിക്കാ closeSidebar = "ലെയറുകളുടെ സൈഡ്‌ബാർ അടയ്ക്കുക" hideAll = "എല്ലാ ലെയറുകളും മറയ്ക്കുക" showAll = "എല്ലാ ലെയറുകളും കാണിക്കുക" +title = "Layers" [viewer.link] delete = "ലിങ്ക് ഇല്ലാതാക്കുക" @@ -11646,6 +11682,7 @@ exitRedaction = "റെഡാക്ഷൻ മോഡിൽ നിന്ന് പ exportAll = "PDF എക്സ്പോർട്ട് ചെയ്യുക" exportSelected = "തിരഞ്ഞെടുത്ത പേജുകൾ എക്സ്പോർട്ട് ചെയ്യുക" formFill = "ഫോം പൂരിപ്പിക്കുക" +hideToolbar = "Hide toolbar" multiTool = "മൾട്ടി-ടൂൾ" panMode = "പാൻ മോഡ്" print = "PDF അച്ചടിക്കുക" @@ -11666,6 +11703,7 @@ selectAll = "എല്ലാം തിരഞ്ഞെടുക്കുക" selectByNumber = "പേജ് നമ്പർ പ്രകാരം തിരഞ്ഞെടുക്കുക" selectLanguage = "ഭാഷ തിരഞ്ഞെടുക്കുക" share = "പങ്കിടുക" +showToolbar = "Show toolbar" toggleAnnotations = "അനോട്ടേഷനുകളുടെ ദൃശ്യത ടോഗിൾ ചെയ്യുക" toggleAttachments = "അറ്റാച്ച്മെന്റുകൾ ടോഗിൾ ചെയ്യുക" toggleBookmarks = "ബുക്ക്മാർക്കുകൾ ടോഗിൾ ചെയ്യുക" diff --git a/frontend/editor/public/locales/nl-NL/translation.toml b/frontend/editor/public/locales/nl-NL/translation.toml index 095989e21d..e65bda307b 100644 --- a/frontend/editor/public/locales/nl-NL/translation.toml +++ b/frontend/editor/public/locales/nl-NL/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabet" apply = "Toepassen" applyAndContinue = "Opslaan en verlaten" -areYouSure = "Weet u zeker dat u wilt vertrekken?" back = "Terug" black = "Zwart" blue = "Blauw" @@ -106,6 +105,7 @@ undoSuccess = "Bewerking succesvol ongedaan gemaakt" unknown = "Onbekend" unpin = "Bestand losmaken (vervangen na tooluitvoering)" unsavedChanges = "U heeft niet-opgeslagen wijzigingen in uw PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Niet-opgeslagen wijzigingen" unsupported = "Niet ondersteund" username = "Gebruikersnaam" @@ -1879,6 +1879,9 @@ width = "Breedte" [app] description = "Het gratis alternatief voor Adobe Acrobat (10M+ downloads)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Converteren naar PDF/A-3b" convertToPdfA3bDescription = "Maakt een archief-PDF met ingesloten bijlagen" @@ -2933,12 +2936,16 @@ text = "Om deze machtigingen niet wijzigbaar te maken, gebruik de tool Wachtwoor [chat.actions] copy = "Bericht kopiëren" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling-agentopties" +assistant = "AI Assistant" clearChat = "Chat wissen" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Deze bewerking gebruikt je cloud-credits" tooltip = "Kleur van het scherm kiezen" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Kies kleur" [common] +actions = "Actions" back = "Terug" cancel = "Annuleren" close = "Sluiten" +codeSample = "Code sample" collapse = "Samenvouwen" confirm = "Bevestigen" continue = "Doorgaan" copied = "Gekopieerd!" copy = "Kopiëren" done = "Gereed" +enterValue = "Enter value" error = "Fout" expand = "Uitvouwen" loading = "Laden..." next = "Volgende" +open = "Open" preview = "Voorbeeld" previous = "Vorige" refresh = "Vernieuwen" remaining = "Resterend" retry = "Opnieuw proberen" save = "Opslaan" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Selectie wissen" @@ -3185,6 +3199,7 @@ title = "Compressiemethode" [compress.settings] desiredSize = "Gewenste bestandsgrootte" desiredSizePlaceholder = "Grootte invoeren" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Compressie is een eenvoudige manier om uw bestandsgrootte te verkleinen. Kies Bestandsgrootte om een doelformaat in te voeren en laat ons de kwaliteit voor u aanpassen. Kies Kwaliteit om de compressiesterkte handmatig in te stellen." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF-opties" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Selecteer eerst een bronformaat" settings = "Instellingen" single = "Enkel" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Zoeken..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Bewerken" @@ -3824,11 +3861,13 @@ shareSelected = "Geselecteerde delen" sharing = "Delen" showAll = "Alles weergeven" showHistory = "Geschiedenis weergeven" +sortBy = "Sort files" sortByDate = "Sorteren op datum" sortByName = "Sorteren op naam" sortBySize = "Sorteren op grootte" storage = "Opslag" storageState = "Opslag" +storageUsed = "Storage used" synced = "Gesynchroniseerd" title = "PDF-bestanden uploaden" toolChain = "Toegepaste tools" @@ -3874,8 +3913,12 @@ addFiles = "Bestanden toevoegen" addingFiles = "Bestanden toevoegen…" collapse = "Zijbalk samenvouwen" customizeGroups = "Groepen aanpassen" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Open bestanden om te beginnen" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Zijbalk uitvouwen" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive is niet geconfigureerd" @@ -3888,15 +3931,17 @@ openFromComputer = "Openen vanaf computer" openSettings = "Instellingen openen" other = "Overig" recent = "Recent" -search = "Zoeken" -searchPlaceholder = "Bestanden zoeken..." viewAll = "Alle {{count}} bestanden bekijken" [fileSidebar.fileItem] closeViewer = "Viewer sluiten" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Openen in viewer" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Terugzetten naar standaardinstellingen" subtitle = "Groepeer je bestanden in hoofdcategorieën. Voeg bestaande of nieuwe labels toe aan een categorie, wijzig de naam ervan of maak je eigen categorie. Bestanden die in geen van je categorieën staan, verschijnen onder “Overig”." title = "Zijbalkcategorieën" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Aan werkruimte toevoegen" addToWorkspaceCount = "{{count}} aan werkruimte toevoegen" @@ -3918,7 +3971,6 @@ backToMyFiles = "Terug naar Mijn bestanden" breadcrumbs = "Mappad" cancel = "Annuleren" classification = "Classificatie" -clearSearch = "Zoekopdracht wissen" clearSelection = "Selectie wissen" closeDetails = "Details sluiten" create = "Maken" @@ -3950,6 +4002,7 @@ downloadAll = "Alles downloaden" downloadVersion = "Deze versie downloaden" dropOverlay = "Sleep bestanden hierheen om te uploaden" dropOverlaySub = "Bestanden beginnen in Lokaal. Gebruik 'Verplaatsen naar' of 'Opslaan in cloud' om ze in een map te organiseren." +duplicate = "Duplicate" file = "Bestand" fileInfo = "File info" fileMenu = "Bestandsacties" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Mapstructuur vergroten/verkleinen (pijltjestoetsen, Shift vo save = "Opslaan" saveToServer = "Opslaan op server" saveToServerDisabledHint = "Opslaan op de server is niet ingeschakeld op deze server. Vraag uw beheerder om dit in te schakelen." -search = "Zoeken" -searchPlaceholder = "Deze map en submappen doorzoeken" selectAll = "Alles selecteren" selectAllHint = "Klik om alles te selecteren. Tip: houd Ctrl (of Cmd) ingedrukt om bestanden één voor één toe te voegen, Shift om een bereik te selecteren." selectedCount = "{{count}} geselecteerd" @@ -4044,6 +4095,10 @@ title = "Nog geen cloudbestanden" hint = "Bestanden die zijn opgeslagen zonder te uploaden blijven hier. Zet een bestand neer om er een toe te voegen." title = "Geen alleen-lokale bestanden" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Bestanden die u opent of bewerkt, verschijnen hier." title = "Nog niets gewijzigd" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Kan map niet verwijderen." deleteFolderFailedDetail = "Kan map niet verwijderen: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Kan mapuiterlijk niet bijwerken." folderAppearanceFailedDetail = "Kan mapuiterlijk niet bijwerken: {{message}}" moveFilesFailed = "Kan bestanden niet verplaatsen." @@ -4113,6 +4170,11 @@ localHint = "Alleen opgeslagen in deze browser" shared = "Gedeeld" sharedHint = "Met u gedeeld via link" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Oudste eerst" modifiedDesc = "Recent eerst" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Geen bestanden beschikbaar in opslag. Open eerst enkele open = "Openen" openFile = "Bestand openen" openFiles = "Bestanden openen" +selectFile = "Select {{name}}" selectFromStorage = "Selecteren uit opslag" upload = "Uploaden" uploadFile = "Bestand uploaden" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Alles opgeslagen" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV extraheren mislukt" extractXlsxError = "XLSX extraheren mislukt" +filled = "filled" flattenAfterFilling = "Afvlakken na invullen" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "verpl." requiredFieldsError = "Vul alle verplichte velden in" rescanFields = "Velden opnieuw scannen" @@ -4795,9 +4863,6 @@ title = "Afbeelding naar PDF" [imageToPdf] tags = "conversie,img,jpg,foto" -[infoBanner] -dismiss = "Negeren" - [invite] acceptError = "Account aanmaken mislukt" accountFor = "Account aanmaken voor" @@ -4979,6 +5044,34 @@ uploadSuccess = "Upload Successful!" uploadSuccessMessage = "Your images have been transferred." validating = "Validating session..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Scan to upload photos. Images auto-convert to PDF." descriptionNoConvert = "Scan to upload photos from your mobile device." @@ -4997,8 +5090,13 @@ title = "Upload from Mobile" tags = "Multitool,meerdere bewerkingen,UI,klik sleep,voorkant,clientzijde,interactief,beweegbaar,verplaats" title = "PDF Multitool" -[navbar] -search = "Zoeken" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Authenticatie is niet geslaagd. U kunt dit venster sluiten en het opnieuw proberen." @@ -5127,6 +5225,7 @@ activeFiles = "De weergave Actieve bestanden toont alle PDF's d allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Sluiten" cropSettings = "Nu we het bestand hebben geselecteerd dat we willen bijsnijden, kunnen we de tool Bijsnijden configureren om het gebied te kiezen waarnaar we de PDF willen bijsnijden." +dialogLabel = "Onboarding" fileCheckbox = "Door op een van de bestanden te klikken selecteert u het voor verwerking. U kunt meerdere bestanden selecteren voor batchbewerkingen." fileReplacement = "Het gewijzigde bestand vervangt automatisch het originele bestand in de Werkbank, zodat u het eenvoudig door meer tools kunt halen." filesButton = "Met de knop Bestanden op de sneltoegangsbalk kunt u PDF's uploaden om de tools op toe te passen." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF naar PDF/A" tags = "archief,langdurig,standaard,conversie,opslag,bewaring" title = "PDF naar PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "print,standaard,conversie,productie,prepress,archief" title = "PDF naar PDF/X" @@ -6045,6 +6151,7 @@ small = "500 Credits" xsmall = "100 Credits" [plan.availablePlans] +currency = "Billing currency" subtitle = "Kies het abonnement dat bij uw behoeften past" title = "Beschikbare abonnementen" @@ -6624,6 +6731,7 @@ reachedTitle = "Maandelijkse uitgavelimiet bereikt" title = "Kon Stripe-portal niet openen" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "van {{allowance}} gratis PDF's gebruikt" capSuffix_other = "van {{allowance}} gratis PDF's gebruikt" eyebrow = "Processor-proefperiode" @@ -6873,6 +6981,32 @@ label = "Jira" description = "E-mail een document of melding wanneer een beleidsregel wordt uitgevoerd." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Sla verwerkte documenten op in Nextcloud." @@ -7077,11 +7211,6 @@ title = "Officiële SDKs" beta = "Beta" deprecated = "Verouderd" -[portal.docs.search] -empty = "Geen overeenkomende documentatie" -placeholder = "Documentatie zoeken" -results = "{{count}} resultaten" - [portal.docs.skills] eyebrow = "VAARDIGHEDEN" lead = "Gebundelde, benoemde mogelijkheden die je agent als één tool aanroept. Elke vaardigheid is een deterministische op-chain met gekoppelde evals." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Geen documenten komen overeen met dit filter." rowActions = "Rijacties" sensitiveLabel = "Gevoelig" -sensitiveTitle = "Gevoelig — toegang vereist" [portal.documents.table.columns] action = "Pipeline / Actie" document = "Document" +labels = "Labels" product = "Product" status = "Status" time = "Tijd" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Laatst gezien" region = "Regio" status = "Status" +target = "Target" version = "Versie" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Deze weergave heeft een onverwachte fout aangetroffen. Probeer he retry = "Opnieuw proberen" title = "Er is iets misgegaan op deze pagina" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Terug" body = "Download de desktop-app of host de server zelf." @@ -7380,11 +7560,6 @@ cancel = "Annuleren" confirm = "Sleutel intrekken" title = "API-sleutel intrekken" -[portal.infrastructure.attestationLabel] -attested = "Geattesteerd" -inScope = "Binnen scope" -notApplicable = "N.v.t." - [portal.infrastructure.audit] filterAriaLabel = "Auditgebeurtenissen filteren op categorie" heading = "Auditlogs" @@ -7456,11 +7631,6 @@ info = "Info" success = "Succes" warning = "Waarschuwing" -[portal.infrastructure.certLabel] -certified = "Gecertificeerd" -inProgress = "In behandeling" -notStarted = "Niet gestart" - [portal.infrastructure.createKey] cancel = "Annuleren" createKey = "Sleutel maken" @@ -7474,238 +7644,10 @@ subtitleCreated = "Kopieer dit geheim nu — het wordt niet opnieuw getoond." title = "API-sleutel maken" titleCreated = "Sleutel aangemaakt" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "In wachtrij" -rolledBack = "Teruggedraaid" -rolling = "Wordt uitgerold" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Geïmplementeerd door" -environment = "Omgeving" -product = "Product" -status = "Status" -version = "Versie" -when = "Wanneer" - -[portal.infrastructure.deployments.recent] -heading = "Recente implementaties" -subheading = "De nieuwste rollouts over producten en omgevingen." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instanties" -latency = "Latentie" -load = "Belasting" -p99 = "P99" -region = "Regio" -status = "Status" -throughput = "Doorvoer" -uptime = "Uptime" -version = "Versie" - -[portal.infrastructure.deployments.regions] -heading = "Regio's" -subheading = "Live gezondheid voor elke geïmplementeerde Stirling-regio — latentie, belasting en rolloutversie." - -[portal.infrastructure.deployments.regions.empty] -description = "Geïmplementeerde regio's verschijnen hier zodra je werkruimte is ingericht." -title = "Geen regio's geïmplementeerd" - [portal.infrastructure.keyLabel] active = "Actief" revoked = "Ingetrokken" -[portal.infrastructure.modelLabel] -active = "Actief" -degraded = "Verslechterd" -disabled = "Uitgeschakeld" - -[portal.infrastructure.models] -heading = "Modellen" -msValue = "{{value}} ms" -subheading = "De modelcatalogus en routering die documentverwerking in je werkruimte aandrijven." - -[portal.infrastructure.models.byom] -description = "Registreer een on-prem of zelfgehost model en pin het aan een regio voor verwerking gebonden aan data residency." -title = "Bring your own model" - -[portal.infrastructure.models.catalogue] -heading = "Catalogus" -sub = "Beheerde modellen beschikbaar voor je werkruimte, met live latentie en kosten." -subEnterprise = "Beheerde, bring-your-own en on-prem modellen — met pinning per regio beschikbaar." - -[portal.infrastructure.models.catalogue.empty] -description = "Modellen in de catalogus van je werkruimte verschijnen hier." -title = "Geen modellen beschikbaar" - -[portal.infrastructure.models.columns] -cost = "Kosten" -latency = "Latentie" -load = "Belasting" -model = "Model" -status = "Status" -type = "Type" -version = "Versie" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/aanroep" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Actieve modellen" -avgLatency = "Gem. latentie" -included = "Inbegrepen" -monthlySpend = "Maandelijkse modeluitgaven" - -[portal.infrastructure.models.routing] -empty = "Geen routeringsregels geconfigureerd." -heading = "Routeringsregels" -sub = "Welk model elke bewerking afhandelt. De standaard wordt toegepast wanneer geen specifiekere regel overeenkomt." -subLocked = "Routeer bewerkingen naar specifieke modellen — beschikbaar met betaalde abonnementen." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Upgrade naar Pro om te bepalen welk model elke bewerking en elk documenttype afhandelt." -title = "Modelroutering is een betaalde functie" - -[portal.infrastructure.models.routingColumns] -default = "Standaard" -docType = "Documenttype" -modelForAria = "Model voor {{operation}}" -operation = "Bewerking" -routedTo = "Gerouteerd naar" - -[portal.infrastructure.modelTypeLabel] -classification = "Classificatie" -extraction = "Extractie" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Verslechterd" -down = "Uitgevallen" -healthy = "Gezond" - -[portal.infrastructure.security.access.byok] -description = "Lever een sleutel uit je eigen KMS. Stirling versleutelt ermee, maar kan nog steeds lezen." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Sleutels verlaten je KMS nooit. Stirling bewaart alleen ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling beheert encryptiesleutels. Het eenvoudigst — geen key ops aan jouw kant." -label = "Door Stirling beheerde sleutels" - -[portal.infrastructure.security.accessPolicy] -heading = "Documenttoegangsbeleid" -subheading = "Bepaalt wie verwerkte documenten in rust kan ontsleutelen." - -[portal.infrastructure.security.attestations] -heading = "Compliance-attestaties" -noReport = "Geen rapport beschikbaar" -subheading = "Auditpostuur per framework, met rapporten beschikbaar voor geattesteerde controles." -viewReport = "Rapport bekijken →" - -[portal.infrastructure.security.compliance] -heading = "Compliance" -subheading = "Attestaties en certificeringen die het Stirling-platform dekken." - -[portal.infrastructure.security.empty] -description = "De beveiligingsconfiguratie van je werkruimte verschijnt hier." -title = "Beveiligingspostuur niet beschikbaar" - -[portal.infrastructure.security.hyokBanner] -description = "Met HYOK verlaten encryptiesleutels je KMS nooit. Stirling bewaart en verwerkt alleen ciphertext die je op elk moment kunt intrekken." -title = "Stirling kan je documenten niet ontsleutelen" - -[portal.infrastructure.security.ipAllowlist] -empty = "Geen IP-bereiken geconfigureerd — alle IP's toegestaan." -heading = "IP allowlist" -sub = "API-toegang is beperkt tot deze CIDR-bereiken." -subLocked = "Beperk API-toegang tot bekende IP-bereiken — beschikbaar met betaalde abonnementen." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Upgrade naar Pro om API-toegang tot specifieke netwerken te beperken." -title = "IP allowlisting is een betaalde functie" - -[portal.infrastructure.security.ipColumns] -added = "Toegevoegd" -addedBy = "Toegevoegd door" -cidr = "CIDR" -label = "Label" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritme" -heading = "Beheer van encryptiesleutels" -keyId = "Sleutel-ID" -lastRotated = "Laatst geroteerd" -rotateKey = "Sleutel roteren" -rotationPolicy = "Rotatiebeleid" -subheading = "Beheer van de sleutels die documenten in rust versleutelen — wie kan ontsleutelen en hoe sleutels roteren." - -[portal.infrastructure.security.managedBanner] -description = "Bring-your-own-key (BYOK) en hold-your-own-key (HYOK) sleutelbeheer zijn beschikbaar met Enterprise. Upgrade om sleutels uit je eigen KMS te leveren." -title = "Sleutels worden door Stirling beheerd in je abonnement" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Azië-Pacific" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR data boundary" -label = "Europese Unie" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Verenigde Staten" - -[portal.infrastructure.security.residencyHeader] -heading = "Data residency" -subheading = "Waar documenten worden opgeslagen en verwerkt." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} gebruikt" - -[portal.infrastructure.storage.empty] -description = "Verbonden opslag en gebruik verschijnen hier." -title = "Geen opslag geconfigureerd" - -[portal.infrastructure.storage.lifecycle] -active = "Actief" -activeRange = "0–{{value}}d" -archived = "Gearchiveerd" -coldStorage = "koude opslag" -deleted = "Verwijderd" -never = "nooit" -purged = "opgeschoond" - -[portal.infrastructure.storage.providers] -connect = "Verbinden" -connected = "Verbonden" -heading = "Verbonden providers" -subheading = "Waar verwerkte artefacten worden weggeschreven." - -[portal.infrastructure.storage.retention] -heading = "Bewaartermijn" -subheading = "Hoe lang artefacten worden bewaard vóór lifecycle-verwijdering." -windowLabel = "Standaard bewaarperiode" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dag" -days_other = "{{count}} dagen" -never = "Nooit verwijderen" - -[portal.infrastructure.storage.totalUsage] -heading = "Totaal gebruik" -progressLabel = "Gebruikte opslag" -subheading = "Opslag gebruikt over alle verbonden providers." - [portal.infrastructure.tabs] apiKeys = "API-sleutels" audit = "Auditlogs" @@ -7715,11 +7657,9 @@ security = "Beveiliging" storage = "Opslag" [portal.integrations] -addAnother = "Nog een toevoegen" availableHeading = "Beschikbaar" comingSoonHeading = "Binnenkort beschikbaar" connect = "Verbinden" -connectedHeading = "Verbonden" connectionCount_one = "{{count}} verbinding" connectionCount_other = "{{count}} verbindingen" customApi = "Aangepaste API" @@ -7737,6 +7677,10 @@ security = "Beveiliging" signing = "Ondertekening" storage = "Opslag" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Verbonden" @@ -7767,7 +7711,6 @@ integrations = "Integraties" pipelines = "Pipelines" policies = "Beleid" procurement = "Inkoop" -settings = "Instellingen" sources = "Bronnen" usage = "Gebruik en facturering" users = "Gebruikers" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Nieuwe pipeline" [portal.pipelines.builder] -addStep = "Tool toevoegen" +activate = "Activate" back = "Terug naar pipelines" cannotFollow = "Kan {{produced}} niet verwerken" chooseAccount = "Kies een account" @@ -7792,28 +7735,38 @@ chooseDestination = "Kies een bestemming" chooseOperation = "Kies wat deze stap doet" chooseSource = "Kies een bron" discard = "Wijzigingen verwerpen" -enabled = "Ingeschakeld" inputs = "Invoer" inputSource = "Invoerbron" inputTrigger = "Trigger" keepEditing = "Blijven bewerken" +moreActions = "More actions" needsConfiguring = "Moet worden ingesteld" -needsUpload = "Vereist een geüpload bestand" -noSources = "Nog geen bronnen verbonden. Maak er een aan om deze als invoer te gebruiken." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Geen tools komen overeen met je zoekopdracht." -pipelineSettings = "Pipeline-instellingen" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Tools zoeken" -selectToolBody = "Voeg een tool toe om je pipeline te bouwen." -selectToolTitle = "Nog geen tools" sendToSystem = "Naar een ander systeem verzenden" stepsIncompatible = "Deze stappen kunnen niet worden uitgevoerd op wat hun vorige stap oplevert: {{tools}}." stepsNeedSetup = "Deze stappen moeten nog worden ingesteld voordat je kunt opslaan: {{tools}}." -toolSettings = "Toolinstellingen" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Niet-herkende bewerking, ongewijzigd behouden." unsavedBody = "Je hebt niet-opgeslagen wijzigingen. Sla ze op voordat je weggaat, of verwerp ze?" unsavedTitle = "Niet-opgeslagen wijzigingen" -uploadUnsupported = "Geüploade bestanden worden nog niet ondersteund in pipelines, dus deze stappen kunnen niet worden opgeslagen: {{tools}}." usesDefaults = "Wordt uitgevoerd met standaardinstellingen" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combineert elk bestand uit de vorige stap" @@ -7825,31 +7778,36 @@ undeclared-operation = "Kan niet controleren wat deze stap accepteert" [portal.pipelines.composer] addTool = "Tool toevoegen" -cancel = "Annuleren" -chainEmpty = "Voeg een tool toe om je pipeline te bouwen." create = "Pipeline maken" +createPaused = "Create paused" editingUnsupported = "Het weergeven van deze toolparams voor bewerking wordt nog niet ondersteund." -moveDown = "Omlaag verplaatsen" -moveUp = "Omhoog verplaatsen" +editSource = "Edit source" name = "Naam" namePlaceholder = "bijv. Redactiesweep" noToolSettings = "Deze tool heeft geen configureerbare instellingen." -operations_one = "Bewerking ({{count}})" -operations_other = "Bewerkingen ({{count}})" output = "Uitvoer" -removeStep = "Bewerking verwijderen" save = "Wijzigingen opslaan" scheduleEvery = "Uitvoeren elke" -sources = "Bronnen" -sourcesLoading = "Bronnen laden..." trigger = "Trigger" triggerManual = "Alleen handmatig" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dagen" hours = "uur" minutes = "minuten" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\" verwijderen? Dit kan niet ongedaan worden gemaakt." cancel = "Annuleren" @@ -7867,6 +7825,37 @@ connectSource = "Een bron verbinden" description = "Maak je eerste pipeline: kies de bronnen waarop deze draait, koppel de bewerkingen en kies waar de uitvoer naartoe gaat." title = "Nog geen pipelines" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Actief" paused = "Gepauzeerd" @@ -7879,7 +7868,6 @@ completed_one = "Run voltooid." completed_other = "Alle {{count}} runs voltooid." empty = "Niets om uit te voeren: de bronnen hadden geen documenten om te verwerken." failed = "Run mislukt: {{error}}" -historyCleared = "Geschiedenis gewist. De volgende uitvoering verwerkt alles opnieuw dat momenteel in de bronnen staat." inFlight = "Niets nieuws om uit te voeren: documenten worden nog verwerkt vanuit een eerdere uitvoering." parked_one = "Niets om uit te voeren: {{count}} document is eerder mislukt en is geparkeerd. Los de oorzaak op en wis daarna de geschiedenis om het opnieuw te proberen." parked_other = "Niets om uit te voeren: {{count}} documenten zijn eerder mislukt en zijn geparkeerd. Los de oorzaak op en wis daarna de geschiedenis om ze opnieuw te proberen." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Bronnen" status = "Status" steps = "Stappen" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Mapbewaking" @@ -8209,6 +8198,24 @@ label = "Aan een Jira-issue toevoegen" description = "Verzendt het verwerkte document als bijlage." label = "Het document e-mailen (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Schrijft het verwerkte document naar een map." label = "Uploaden naar Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Maximum bereikt" subtitle = "Je gratis trial loopt tot en met {{date}}. Geen kaart vereist." title = "Enterprise-trial" -[portal.search] -ariaLabel = "Zoeken" -placeholder = "Zoek in Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Snelle acties verschijnen hier zodra ze beschikbaar zijn." -noActionsTitle = "Geen snelle acties" -noMatches = "Geen overeenkomsten voor \"{{query}}\"" -noMatchesDescription = "Probeer een ander trefwoord of blader door de catalogus." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Annuleren" confirm = "Verwijderen" title = "Bron verwijderen?" -[portal.sources.empty] -description = "Verbind een map (en binnenkort cloudopslag) zodat je beleidsregels ergens documenten vandaan kunnen halen." -title = "Nog geen bronnen verbonden" - [portal.sources.kpi] inUse = "In gebruik" total = "Verbindingen" @@ -8785,6 +8778,7 @@ unused = "Ongebruikt" documents = "Documenten" source = "Bron" status = "Status" +type = "Type" usedBy = "Beleidsregels" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Beleid & Privacy" [settings.preferences] title = "Voorkeuren" -[settings.search] -placeholder = "Instellingen doorzoeken..." - [settings.security] description = "Werk uw wachtwoord bij om uw account veilig te houden." title = "Beveiliging" @@ -10128,6 +10119,7 @@ last = "Laatste pagina" maintainRatio = "Aspectverhouding behouden" next = "Volgende pagina" noSavedSigs = "Geen opgeslagen signatuuren gevonden" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Persoonlijke Signatuuren" previous = "Vorige pagina" redo = "Opnieuw" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Verwijdering van achtergrond is mislukt" hint = "Upload een PNG- of JPG-afbeelding van uw handtekening" label = "Handtekeningafbeelding uploaden" placeholder = "Afbeeldingsbestand selecteren" +previewAlt = "Current image signature" processing = "Afbeelding verwerken..." removeBackground = "Witte achtergrond verwijderen (transparant maken)" @@ -10179,6 +10172,17 @@ saved = "Selecteer hierboven een opgeslagen handtekening en klik vervolgens erge text = "Na het invoeren van uw naam hierboven, klik ergens op de PDF om uw handtekening te plaatsen." title = "Hoe een handtekening toevoegen" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Handtekening verplaatsen" pause = "Plaatsing pauzeren" @@ -10625,6 +10629,23 @@ title = "Uploaden naar server" updateButton = "Bijwerken op server" uploadButton = "Uploaden naar server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Ga terug" pdfTools = "PDF-tools" placeholder = "Kies een tool om te beginnen" premiumFeature = "Premiumfunctie:" -searchTools = "Tools zoeken" toolsHeader = "Tools" viewAllTools = "Alle tools weergeven" @@ -10822,6 +10842,9 @@ removal = "Verwijdering" signing = "Ondertekenen" verification = "Verificatie" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Geen tools gevonden" noTools = "Geen tools beschikbaar" @@ -10979,6 +11002,13 @@ approver = "Keurt beleidsregel goed" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Uitnodiging voor {{email}} annuleren? Deze persoon kan niet deelnemen met de huidige link." cancelInviteTitle = "Uitnodiging annuleren" @@ -10998,10 +11028,8 @@ title = "Nog geen leden" addToTeam = "Toevoegen aan team" guestCount = "{{count}} gast" guests = "Gasten" -guestsDesc = "Externe samenwerkers, beperkt tot wat je hebt gedeeld. Alleen Editor." ledBy = "geleid door {{owner}}" org = "Organisatie" -orgDesc = "Eigenaren met organisatiebrede bevoegdheid en beleidsgoedkeuring" owners = "{{count}} eigenaar" team = "Team {{name}}" teamMeta = "{{count}} personen" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Uitgenodigd door {{who}}" cancel = "Annuleren" -count = "{{count}} in behandeling" -desc = "Uitgenodigde personen die nog niet zijn toegetreden. Ze houden een plek bezet totdat ze accepteren." expiresInDays_one = "Verloopt over {{count}} dag" expiresInDays_other = "Verloopt over {{count}} dagen" expiresToday = "Verloopt vandaag" title = "Uitnodigingen in behandeling" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Er is iets misgegaan bij het bereiken van de backend, of je hebt geen toegang. Probeer het opnieuw." title = "Kon leden niet laden" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Bijlagen zoeken" title = "Bijlagen" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Bladwijzertitel" bookmarkTitleRequired = "Bladwijzertitel is vereist" closeSidebar = "Zijbalk met bladwijzers sluiten" collapseAll = "Alle bladwijzers samenvouwen" +empty = "No bookmarks in this document" expandAll = "Alle bladwijzers uitvouwen" searchPlaceholder = "Bladwijzers zoeken" +title = "Bookmarks" [viewer.comments] addComment = "Opmerking toevoegen" @@ -11264,10 +11297,12 @@ locateAnnotation = "Zoeken in document" moreActions = "Meer acties" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Pagina {{page}}" placingHint = "Klik op een pagina om te plaatsen… (annuleren)" removeCommentOnly = "Alleen opmerking verwijderen" saveReply = "Antwoord opslaan" +searchPlaceholder = "Search comments" title = "Opmerkingen" typeComment = "Opmerking" typeInsertText = "Tekst invoegen" @@ -11290,6 +11325,7 @@ unsavedDesc = "U heeft niet-opgeslagen wijzigingen" closeSidebar = "Zijbalk met lagen sluiten" hideAll = "Alle lagen verbergen" showAll = "Alle lagen tonen" +title = "Layers" [viewer.link] delete = "Link verwijderen" @@ -11646,6 +11682,7 @@ exitRedaction = "Zwartlakmodus afsluiten" exportAll = "PDF exporteren" exportSelected = "Geselecteerde pagina's exporteren" formFill = "Formulier invullen" +hideToolbar = "Hide toolbar" multiTool = "Multi-tool" panMode = "Pan-modus" print = "PDF afdrukken" @@ -11666,6 +11703,7 @@ selectAll = "Alles selecteren" selectByNumber = "Selecteren op paginanummers" selectLanguage = "Taal selecteren" share = "Delen" +showToolbar = "Show toolbar" toggleAnnotations = "Annotaties tonen/verbergen" toggleAttachments = "Bijlagen tonen/verbergen" toggleBookmarks = "Bladwijzers tonen/verbergen" diff --git a/frontend/editor/public/locales/no-NB/translation.toml b/frontend/editor/public/locales/no-NB/translation.toml index eef3319e76..307e603d81 100644 --- a/frontend/editor/public/locales/no-NB/translation.toml +++ b/frontend/editor/public/locales/no-NB/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabet" apply = "Bruk" applyAndContinue = "Lagre og forlat" -areYouSure = "Er du sikker på at du vil forlate?" back = "Tilbake" black = "Svart" blue = "Blå" @@ -106,6 +105,7 @@ undoSuccess = "Operasjon angret" unknown = "Ukjent" unpin = "Løsne fil (erstatt etter verktøykjøring)" unsavedChanges = "Du har ulagrede endringer i PDF-en." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Ulagrede endringer" unsupported = "Ikke støttet" username = "Brukernavn" @@ -1879,6 +1879,9 @@ width = "Bredde" [app] description = "Det gratis alternativet til Adobe Acrobat (10M+ nedlastinger)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Konverter til PDF/A-3b" convertToPdfA3bDescription = "Oppretter en arkiv-PDF med innebygde vedlegg" @@ -2933,12 +2936,16 @@ text = "For å gjøre disse tillatelsene uforanderlige, bruk verktøyet Legg til [chat.actions] copy = "Kopier melding" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling-agentalternativer" +assistant = "AI Assistant" clearChat = "Tøm chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Denne operasjonen vil bruke skykreditter" tooltip = "Velg farge fra skjermen" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Velg farge" [common] +actions = "Actions" back = "Tilbake" cancel = "Avbryt" close = "Lukk" +codeSample = "Code sample" collapse = "Slå sammen" confirm = "Bekreft" continue = "Fortsett" copied = "Kopiert!" copy = "Kopier" done = "Ferdig" +enterValue = "Enter value" error = "Feil" expand = "Utvid" loading = "Laster..." next = "Neste" +open = "Open" preview = "Forhåndsvis" previous = "Forrige" refresh = "Oppdater" remaining = "Gjenstår" retry = "Prøv igjen" save = "Lagre" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Tøm valgte" @@ -3185,6 +3199,7 @@ title = "Komprimeringsmetode" [compress.settings] desiredSize = "Ønsket filstørrelse" desiredSizePlaceholder = "Angi størrelse" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Komprimering er en enkel måte å redusere filstørrelsen på. Velg Filstørrelse for å angi en målstørrelse og la oss justere kvaliteten for deg. Velg Kvalitet for å sette komprimeringsstyrke manuelt." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF-alternativer" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Velg et kildeformat først" settings = "Innstillinger" single = "Enkel" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Søk..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Rediger" @@ -3824,11 +3861,13 @@ shareSelected = "Del valgte" sharing = "Deling" showAll = "Vis alle" showHistory = "Vis historikk" +sortBy = "Sort files" sortByDate = "Sorter etter dato" sortByName = "Sorter etter navn" sortBySize = "Sorter etter størrelse" storage = "Lagring" storageState = "Lagring" +storageUsed = "Storage used" synced = "Synkronisert" title = "Last opp PDF-filer" toolChain = "Brukte verktøy" @@ -3874,8 +3913,12 @@ addFiles = "Legg til filer" addingFiles = "Legger til filer…" collapse = "Skjul sidepanel" customizeGroups = "Tilpass grupper" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Åpne filer for å komme i gang" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Utvid sidepanel" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive er ikke konfigurert" @@ -3888,15 +3931,17 @@ openFromComputer = "Åpne fra datamaskin" openSettings = "Åpne innstillinger" other = "Annet" recent = "Nylig" -search = "Søk" -searchPlaceholder = "Søk i filer..." viewAll = "Vis alle {{count}} filer" [fileSidebar.fileItem] closeViewer = "Lukk fremviser" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Åpne i fremviser" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Tilbakestill til standarder" subtitle = "Grupper filene dine i overordnede kategorier. Legg til eksisterende eller nye etiketter i en kategori, gi den nytt navn, eller opprett din egen. Filer som ikke er i noen av kategoriene dine, vises under «Annet»." title = "Sidefeltkategorier" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Legg til i arbeidsområde" addToWorkspaceCount = "Legg {{count}} til i arbeidsområde" @@ -3918,7 +3971,6 @@ backToMyFiles = "Tilbake til Mine filer" breadcrumbs = "Mappesti" cancel = "Avbryt" classification = "Klassifisering" -clearSearch = "Tøm søk" clearSelection = "Tøm utvalg" closeDetails = "Lukk detaljer" create = "Opprett" @@ -3950,6 +4002,7 @@ downloadAll = "Last ned alle" downloadVersion = "Last ned denne versjonen" dropOverlay = "Slipp filer for å laste opp" dropOverlaySub = "Filer starter i Lokal. Bruk 'Flytt til' eller 'Lagre til skyen' for å organisere dem i en mappe." +duplicate = "Duplicate" file = "Fil" fileInfo = "File info" fileMenu = "Filhandlinger" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Endre størrelse på mappetre (piltaster, Shift for større save = "Lagre" saveToServer = "Lagre til server" saveToServerDisabledHint = "Lagring til serveren er ikke aktivert på denne serveren. Be administratoren om å aktivere det." -search = "Søk" -searchPlaceholder = "Søk i denne mappen og undermapper" selectAll = "Velg alt" selectAllHint = "Klikk for å velge alt. Tips: hold Ctrl (eller Cmd) for å legge til filer én om gangen, Shift for å velge et område." selectedCount = "{{count}} valgt" @@ -4044,6 +4095,10 @@ title = "Ingen skyfiler ennå" hint = "Filer lagret uten opplasting blir værende her. Slipp en fil for å legge til en." title = "Ingen kun-lokale filer" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Filer du åpner eller redigerer, vises her." title = "Ingenting endret ennå" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Kunne ikke slette mappe." deleteFolderFailedDetail = "Kunne ikke slette mappe: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Kunne ikke oppdatere mappeutseende." folderAppearanceFailedDetail = "Kunne ikke oppdatere mappeutseende: {{message}}" moveFilesFailed = "Kunne ikke flytte filer." @@ -4113,6 +4170,11 @@ localHint = "Kun lagret i denne nettleseren" shared = "Delt" sharedHint = "Delt med deg via lenke" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Eldste først" modifiedDesc = "Nyeste først" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Ingen filer tilgjengelig i lagringen. Åpne noen filer f open = "Åpne" openFile = "Åpne fil" openFiles = "Åpne filer" +selectFile = "Select {{name}}" selectFromStorage = "Velg fra lagring" upload = "Last opp" uploadFile = "Last opp fil" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Alt lagret" +analyzingFields = "Analysing form fields..." extractCsvError = "Kunne ikke trekke ut CSV" extractXlsxError = "Kunne ikke trekke ut XLSX" +filled = "filled" flattenAfterFilling = "Flate ut etter utfylling" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obl" requiredFieldsError = "Fyll ut alle obligatoriske felt" rescanFields = "Skann felter på nytt" @@ -4795,9 +4863,6 @@ title = "Bilde til PDF" [imageToPdf] tags = "konvertering,bilde,jpg,foto" -[infoBanner] -dismiss = "Lukk" - [invite] acceptError = "Kunne ikke opprette konto" accountFor = "Oppretter konto for" @@ -4979,6 +5044,34 @@ uploadSuccess = "Opplasting vellykket!" uploadSuccessMessage = "Bildene dine er overført." validating = "Validerer økt..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Skann for å laste opp bilder. Bilder konverteres automatisk til PDF." descriptionNoConvert = "Skann for å laste opp bilder fra mobilenheten din." @@ -4997,8 +5090,13 @@ title = "Last opp fra mobil" tags = "Multi Verktøy,Multi operasjon,UI,klikk dra,frontend,klientside,interaktiv,bevegelig" title = "PDF-multiverktøy" -[navbar] -search = "Søk" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Autentiseringen var ikke vellykket. Du kan lukke dette vinduet og prøve igjen." @@ -5127,6 +5225,7 @@ activeFiles = "Visningen Aktive filer viser alle PDF-ene du har allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Lukk" cropSettings = "Nå som vi har valgt filen vi vil beskjære, kan vi konfigurere Beskjær-verktøyet til å velge området vi vil beskjære PDF-en til." +dialogLabel = "Onboarding" fileCheckbox = "Å klikke på en av filene velger den for behandling. Du kan velge flere filer for batchoperasjoner." fileReplacement = "Den endrede filen vil erstatte originalfilen i Arbeidsbenken automatisk, slik at du enkelt kan kjøre den gjennom flere verktøy." filesButton = "Knappen Filer på hurtigtilgangslinjen lar deg laste opp PDF-er til bruk i verktøyene." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF til PDF/A" tags = "arkiv,langtidslagring,standard,konvertering,lagring,bevaring" title = "PDF til PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "utskrift,standard,konvertering,produksjon,trykklargjøring,arkiv" title = "PDF til PDF/X" @@ -6045,6 +6151,7 @@ small = "500 kreditter" xsmall = "100 kreditter" [plan.availablePlans] +currency = "Billing currency" subtitle = "Velg planen som passer dine behov" title = "Tilgjengelige planer" @@ -6624,6 +6731,7 @@ reachedTitle = "Månedlig forbruksgrense nådd" title = "Kunne ikke åpne Stripe-portalen" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "av {{allowance}} gratis PDF-er brukt" capSuffix_other = "av {{allowance}} gratis PDF-er brukt" eyebrow = "Processor-prøveperiode" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Send et dokument eller et varsel på e-post når en policy kjører." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Arkiver behandlede dokumenter i Nextcloud." @@ -7077,11 +7211,6 @@ title = "Offisielle SDK-er" beta = "Beta" deprecated = "Utdatert" -[portal.docs.search] -empty = "Ingen samsvarende dokumentasjon" -placeholder = "Søk i dokumentasjon" -results = "{{count}} resultater" - [portal.docs.skills] eyebrow = "FERDIGHETER" lead = "Samlede, navngitte kapabiliteter agenten din påkaller som ett enkelt verktøy. Hver ferdighet er en deterministisk operasjonskjede med evalueringer vedlagt." @@ -7180,11 +7309,11 @@ editorAction = "Redigerer" empty = "Ingen dokumenter samsvarer med dette filteret." rowActions = "Radhandlinger" sensitiveLabel = "Sensitivt" -sensitiveTitle = "Sensitivt — tilgang kreves" [portal.documents.table.columns] action = "Pipeline / handling" document = "Dokument" +labels = "Labels" product = "Produkt" status = "Status" time = "Tid" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Sist sett" region = "Region" status = "Status" +target = "Target" version = "Versjon" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Denne visningen traff en uventet feil. Prøv igjen, eller velg en retry = "Prøv igjen" title = "Noe gikk galt på denne siden" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Tilbake" body = "Last ned skrivebordsappen eller selvhost serveren." @@ -7380,11 +7560,6 @@ cancel = "Avbryt" confirm = "Tilbakekall nøkkel" title = "Tilbakekall API-nøkkel" -[portal.infrastructure.attestationLabel] -attested = "Attestert" -inScope = "Innenfor omfang" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtrer revisjonshendelser etter kategori" heading = "Revisjonslogger" @@ -7456,11 +7631,6 @@ info = "Info" success = "Vellykket" warning = "Advarsel" -[portal.infrastructure.certLabel] -certified = "Sertifisert" -inProgress = "Pågår" -notStarted = "Ikke startet" - [portal.infrastructure.createKey] cancel = "Avbryt" createKey = "Opprett nøkkel" @@ -7474,238 +7644,10 @@ subtitleCreated = "Kopier denne hemmeligheten nå — den vises ikke igjen." title = "Opprett API-nøkkel" titleCreated = "Nøkkel opprettet" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "I kø" -rolledBack = "Rullet tilbake" -rolling = "Ruller ut" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Distribuert av" -environment = "Miljø" -product = "Produkt" -status = "Status" -version = "Versjon" -when = "Når" - -[portal.infrastructure.deployments.recent] -heading = "Nylige distribusjoner" -subheading = "De nyeste utrullingene på tvers av produkter og miljøer." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instanser" -latency = "Latens" -load = "Belastning" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Gjennomstrømning" -uptime = "Oppetid" -version = "Versjon" - -[portal.infrastructure.deployments.regions] -heading = "Regioner" -subheading = "Sanntidshelse for hver distribuerte Stirling-region — latens, belastning og utrullingsversjon." - -[portal.infrastructure.deployments.regions.empty] -description = "Distribuerte regioner vises her når arbeidsområdet ditt er klargjort." -title = "Ingen regioner distribuert" - [portal.infrastructure.keyLabel] active = "Aktiv" revoked = "Tilbakekalt" -[portal.infrastructure.modelLabel] -active = "Aktiv" -degraded = "Redusert" -disabled = "Deaktivert" - -[portal.infrastructure.models] -heading = "Modeller" -msValue = "{{value}} ms" -subheading = "Modellkatalogen og rutingen som driver dokumentbehandling på tvers av arbeidsområdet ditt." - -[portal.infrastructure.models.byom] -description = "Registrer en lokal eller selvhostet modell og fest den til en region for behandling bundet til datalokalitet." -title = "Ta med din egen modell" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Administrerte modeller tilgjengelige for arbeidsområdet ditt, med sanntidslatens og kostnad." -subEnterprise = "Administrerte, egne og lokale modeller — med festing per region tilgjengelig." - -[portal.infrastructure.models.catalogue.empty] -description = "Modeller i katalogen til arbeidsområdet ditt vises her." -title = "Ingen modeller tilgjengelige" - -[portal.infrastructure.models.columns] -cost = "Kostnad" -latency = "Latens" -load = "Belastning" -model = "Modell" -status = "Status" -type = "Type" -version = "Versjon" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/kall" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktive modeller" -avgLatency = "Gj.sn. latens" -included = "Inkludert" -monthlySpend = "Månedlige modellkostnader" - -[portal.infrastructure.models.routing] -empty = "Ingen rutingsregler konfigurert." -heading = "Rutingsregler" -sub = "Hvilken modell som håndterer hver operasjon. Standarden brukes når ingen smalere regel samsvarer." -subLocked = "Rut operasjoner til spesifikke modeller — tilgjengelig med betalte planer." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Oppgrader til Pro for å kontrollere hvilken modell som håndterer hver operasjon og dokumenttype." -title = "Modellruting er en betalt funksjon" - -[portal.infrastructure.models.routingColumns] -default = "Standard" -docType = "Dokumenttype" -modelForAria = "Modell for {{operation}}" -operation = "Operasjon" -routedTo = "Rutet til" - -[portal.infrastructure.modelTypeLabel] -classification = "Klassifisering" -extraction = "Ekstraksjon" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Redusert" -down = "Nede" -healthy = "Frisk" - -[portal.infrastructure.security.access.byok] -description = "Oppgi en nøkkel fra din egen KMS. Stirling krypterer med den, men kan fortsatt lese." -label = "Ta med din egen nøkkel (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Nøkler forlater aldri din KMS. Stirling holder bare chiffertekst." -label = "Hold din egen nøkkel (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling administrerer krypteringsnøkler. Enklest — null nøkkeldrift på din side." -label = "Nøkler holdt av Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Policy for dokumenttilgang" -subheading = "Kontrollerer hvem som kan dekryptere behandlede dokumenter i ro." - -[portal.infrastructure.security.attestations] -heading = "Samsvarsattestasjoner" -noReport = "Ingen rapport tilgjengelig" -subheading = "Revisjonsstilling rammeverk for rammeverk, med rapporter tilgjengelige for attesterte kontroller." -viewReport = "Vis rapport →" - -[portal.infrastructure.security.compliance] -heading = "Samsvar" -subheading = "Attestasjoner og sertifiseringer som dekker Stirling-plattformen." - -[portal.infrastructure.security.empty] -description = "Sikkerhetskonfigurasjonen for arbeidsområdet ditt vises her." -title = "Sikkerhetsstilling utilgjengelig" - -[portal.infrastructure.security.hyokBanner] -description = "Med HYOK forlater krypteringsnøkler aldri din KMS. Stirling lagrer og behandler bare chiffertekst som du kan tilbakekalle når som helst." -title = "Stirling kan ikke dekryptere dokumentene dine" - -[portal.infrastructure.security.ipAllowlist] -empty = "Ingen IP-områder konfigurert — alle IP-er tillatt." -heading = "IP-tillatelsesliste" -sub = "API-tilgang er begrenset til disse CIDR-områdene." -subLocked = "Begrens API-tilgang til kjente IP-områder — tilgjengelig med betalte planer." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Oppgrader til Pro for å begrense API-tilgang til spesifikke nettverk." -title = "IP-tillatelseslisting er en betalt funksjon" - -[portal.infrastructure.security.ipColumns] -added = "Lagt til" -addedBy = "Lagt til av" -cidr = "CIDR" -label = "Etikett" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritme" -heading = "Administrasjon av krypteringsnøkler" -keyId = "Nøkkelidentifikator" -lastRotated = "Sist rotert" -rotateKey = "Roter nøkkel" -rotationPolicy = "Rotasjonspolicy" -subheading = "Forvaring av nøklene som krypterer dokumenter i ro — hvem som kan dekryptere, og hvordan nøkler roteres." - -[portal.infrastructure.security.managedBanner] -description = "Forvaring med ta-med-din-egen-nøkkel (BYOK) og hold-din-egen-nøkkel (HYOK) er tilgjengelig på Enterprise. Oppgrader for å oppgi nøkler fra din egen KMS." -title = "Nøkler administreres av Stirling på planen din" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia-Stillehavet" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR-datagrense" -label = "Den europeiske union" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "USA" - -[portal.infrastructure.security.residencyHeader] -heading = "Datalokalitet" -subheading = "Hvor dokumenter lagres og behandles." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} brukt" - -[portal.infrastructure.storage.empty] -description = "Tilkoblet lagring og bruk vises her." -title = "Ingen lagring konfigurert" - -[portal.infrastructure.storage.lifecycle] -active = "Aktiv" -activeRange = "0–{{value}}d" -archived = "Arkivert" -coldStorage = "kaldlagring" -deleted = "Slettet" -never = "aldri" -purged = "slettet permanent" - -[portal.infrastructure.storage.providers] -connect = "Koble til" -connected = "Tilkoblet" -heading = "Tilkoblede leverandører" -subheading = "Hvor behandlede artefakter skrives." - -[portal.infrastructure.storage.retention] -heading = "Oppbevaring" -subheading = "Hvor lenge artefakter beholdes før livssyklussletting." -windowLabel = "Standard oppbevaringsvindu" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dag" -days_other = "{{count}} dager" -never = "Slett aldri" - -[portal.infrastructure.storage.totalUsage] -heading = "Total bruk" -progressLabel = "Lagring brukt" -subheading = "Lagring brukt på tvers av alle tilkoblede leverandører." - [portal.infrastructure.tabs] apiKeys = "API-nøkler" audit = "Revisjonslogger" @@ -7715,11 +7657,9 @@ security = "Sikkerhet" storage = "Lagring" [portal.integrations] -addAnother = "Legg til en til" availableHeading = "Tilgjengelig" comingSoonHeading = "Kommer snart" connect = "Koble til" -connectedHeading = "Tilkoblet" connectionCount_one = "{{count}} tilkobling" connectionCount_other = "{{count}} tilkoblinger" customApi = "Tilpasset API" @@ -7737,6 +7677,10 @@ security = "Sikkerhet" signing = "Signering" storage = "Lagring" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Tilkoblet" @@ -7767,7 +7711,6 @@ integrations = "Integrasjoner" pipelines = "Pipelines" policies = "Policyer" procurement = "Innkjøp" -settings = "Innstillinger" sources = "Kilder" usage = "Bruk og fakturering" users = "Brukere" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Ny pipeline" [portal.pipelines.builder] -addStep = "Legg til verktøy" +activate = "Activate" back = "Tilbake til pipelines" cannotFollow = "Kan ikke ta {{produced}}" chooseAccount = "Velg en konto" @@ -7792,28 +7735,38 @@ chooseDestination = "Velg en destinasjon" chooseOperation = "Velg hva dette trinnet gjør" chooseSource = "Velg en kilde" discard = "Forkast endringer" -enabled = "Aktivert" inputs = "Inndata" inputSource = "Inndatakilde" inputTrigger = "Utløser" keepEditing = "Fortsett å redigere" +moreActions = "More actions" needsConfiguring = "Må settes opp" -needsUpload = "Trenger en opplastet fil" -noSources = "Ingen kilder er tilkoblet ennå. Opprett en for å bruke den som inndata." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Ingen verktøy samsvarer med søket ditt." -pipelineSettings = "Pipeline-innstillinger" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Søk i verktøy" -selectToolBody = "Legg til et verktøy for å bygge pipelinen din." -selectToolTitle = "Ingen verktøy ennå" sendToSystem = "Send til et annet system" stepsIncompatible = "Disse trinnene kan ikke kjøres på det forrige trinnet produserer: {{tools}}." stepsNeedSetup = "Disse trinnene må fortsatt settes opp før lagring: {{tools}}." -toolSettings = "Verktøyinnstillinger" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Ukjent operasjon, beholdt som den er." unsavedBody = "Du har ulagrede endringer. Lagre dem før du forlater, eller forkast dem?" unsavedTitle = "Ulagrede endringer" -uploadUnsupported = "Opplastede filer støttes ikke i pipelines ennå, så disse trinnene kan ikke lagres: {{tools}}." usesDefaults = "Kjører med standardinnstillinger" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kombinerer hver fil fra forrige trinn" @@ -7825,31 +7778,36 @@ undeclared-operation = "Kan ikke sjekke hva dette trinnet godtar" [portal.pipelines.composer] addTool = "Legg til verktøy" -cancel = "Avbryt" -chainEmpty = "Legg til et verktøy for å begynne å bygge pipelinen din." create = "Opprett pipeline" +createPaused = "Create paused" editingUnsupported = "Visning av disse verktøyparametrene for redigering støttes ikke ennå." -moveDown = "Flytt ned" -moveUp = "Flytt opp" +editSource = "Edit source" name = "Navn" namePlaceholder = "f.eks. Redigeringsgjennomgang" noToolSettings = "Dette verktøyet har ingen konfigurerbare innstillinger." -operations_one = "Operasjon ({{count}})" -operations_other = "Operasjoner ({{count}})" output = "Utdata" -removeStep = "Fjern operasjon" save = "Lagre endringer" scheduleEvery = "Kjør hver" -sources = "Kilder" -sourcesLoading = "Laster kilder..." trigger = "Utløser" triggerManual = "Kun manuelt" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dager" hours = "timer" minutes = "minutter" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Slette \"{{name}}\"? Dette kan ikke angres." cancel = "Avbryt" @@ -7867,6 +7825,37 @@ connectSource = "Koble til en kilde" description = "Opprett din første pipeline: velg kildene den kjører over, kjed operasjonene, og velg hvor utdata skal gå." title = "Ingen pipelines ennå" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktive" paused = "Satt på pause" @@ -7879,7 +7868,6 @@ completed_one = "Kjøring fullført." completed_other = "Alle {{count}} kjøringer fullført." empty = "Ingenting å kjøre: kildene hadde ingen dokumenter å behandle." failed = "Kjøring mislyktes: {{error}}" -historyCleared = "Historikk tømt. Neste kjøring behandler alt som for øyeblikket er i kildene, på nytt." inFlight = "Ingenting nytt å kjøre: dokumenter behandles fortsatt fra en tidligere kjøring." parked_one = "Ingenting å kjøre: {{count}} dokument feilet tidligere og er parkert. Rett årsaken, og tøm deretter historikken for å prøve på nytt." parked_other = "Ingenting å kjøre: {{count}} dokumenter feilet tidligere og er parkert. Rett årsaken, og tøm deretter historikken for å prøve dem på nytt." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Kilder" status = "Status" steps = "Trinn" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Mappeovervåking" @@ -8209,6 +8198,24 @@ label = "Legg ved i en Jira-sak" description = "Sender det behandlede dokumentet som et vedlegg." label = "Send dokumentet på e-post (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Skriver det behandlede dokumentet til en mappe." label = "Last opp til Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Maksimert" subtitle = "Den gratis prøveperioden din varer til {{date}}. Ingen kort kreves." title = "Enterprise-prøveperiode" -[portal.search] -ariaLabel = "Søk" -placeholder = "Søk i Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Hurtighandlinger vises her når de er tilgjengelige." -noActionsTitle = "Ingen hurtighandlinger" -noMatches = "Ingen treff for \"{{query}}\"" -noMatchesDescription = "Prøv et annet søkeord eller bla gjennom katalogen." - [portal.settings.groups] admin = "Administrator" @@ -8743,10 +8740,6 @@ cancel = "Avbryt" confirm = "Slett" title = "Slette kilde?" -[portal.sources.empty] -description = "Koble til en mappe (og snart skylagring) slik at policyene dine har et sted å hente dokumenter fra." -title = "Ingen kilder koblet til ennå" - [portal.sources.kpi] inUse = "I bruk" total = "Koblinger" @@ -8785,6 +8778,7 @@ unused = "Ubrukt" documents = "Dokumenter" source = "Kilde" status = "Status" +type = "Type" usedBy = "Policyer" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Retningslinjer og personvern" [settings.preferences] title = "Preferanser" -[settings.search] -placeholder = "Søk i innstillingssider..." - [settings.security] description = "Oppdater passordet ditt for å holde kontoen sikker." title = "Sikkerhet" @@ -10128,6 +10119,7 @@ last = "Siste side" maintainRatio = "Bytt behold sideforhold" next = "Neste side" noSavedSigs = "Ingen lagrede signaturer funnet" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Personlige signaturer" previous = "Forrige side" redo = "Gjør om" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Fjerning av bakgrunn mislyktes" hint = "Last opp et PNG- eller JPG-bilde av signaturen din" label = "Last opp signaturbilde" placeholder = "Velg bildefil" +previewAlt = "Current image signature" processing = "Behandler bilde..." removeBackground = "Fjern hvit bakgrunn (gjør gjennomsiktig)" @@ -10179,6 +10172,17 @@ saved = "Velg en lagret signatur ovenfor, og klikk hvor som helst på PDF-en for text = "Når du har skrevet inn navnet ditt over, klikk hvor som helst i PDF-en for å plassere signaturen." title = "Slik legger du til signatur" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Flytt signatur" pause = "Pause plassering" @@ -10625,6 +10629,23 @@ title = "Last opp til server" updateButton = "Oppdater på server" uploadButton = "Last opp til server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Gå tilbake" pdfTools = "PDF-verktøy" placeholder = "Velg et verktøy for å komme i gang" premiumFeature = "Premium-funksjon:" -searchTools = "Søk etter verktøy" toolsHeader = "Verktøy" viewAllTools = "Vis alle verktøy" @@ -10822,6 +10842,9 @@ removal = "Fjerning" signing = "Signering" verification = "Verifisering" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Ingen verktøy funnet" noTools = "Ingen verktøy tilgjengelig" @@ -10979,6 +11002,13 @@ approver = "Godkjenner policy" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Avbryt invitasjonen til {{email}}? De vil ikke kunne bli med med den nåværende lenken." cancelInviteTitle = "Avbryt invitasjon" @@ -10998,10 +11028,8 @@ title = "Ingen medlemmer ennå" addToTeam = "Legg til i team" guestCount = "{{count}} gjest" guests = "Gjester" -guestsDesc = "Eksterne samarbeidspartnere, begrenset til det du delte. Kun Editor." ledBy = "ledet av {{owner}}" org = "Organisasjon" -orgDesc = "Eiere med myndighet på tvers av organisasjonen og policygodkjenning" owners = "{{count}} eier" team = "{{name}}-team" teamMeta = "{{count}} personer" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Invitert av {{who}}" cancel = "Avbryt" -count = "{{count}} ventende" -desc = "Inviterte personer som ikke har blitt med ennå. De opptar en plass til de godtar." expiresInDays_one = "Utløper om {{count}} dag" expiresInDays_other = "Utløper om {{count}} dager" expiresToday = "Utløper i dag" title = "Ventende invitasjoner" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Noe gikk galt ved kontakt med backend, eller du har ikke tilgang. Prøv igjen." title = "Kunne ikke laste inn medlemmer" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Søk i vedlegg" title = "Vedlegg" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Bokmerketittel" bookmarkTitleRequired = "Bokmerketittel er påkrevd" closeSidebar = "Lukk sidepanelet for bokmerker" collapseAll = "Skjul alle bokmerker" +empty = "No bookmarks in this document" expandAll = "Utvid alle bokmerker" searchPlaceholder = "Søk i bokmerker" +title = "Bookmarks" [viewer.comments] addComment = "Legg til kommentar" @@ -11264,10 +11297,12 @@ locateAnnotation = "Finn i dokument" moreActions = "Flere handlinger" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Side {{page}}" placingHint = "Klikk på en side for å plassere… (avbryt)" removeCommentOnly = "Fjern bare kommentaren" saveReply = "Lagre svar" +searchPlaceholder = "Search comments" title = "Kommentarer" typeComment = "Kommentar" typeInsertText = "Sett inn tekst" @@ -11290,6 +11325,7 @@ unsavedDesc = "Du har ulagrede endringer" closeSidebar = "Lukk sidepanelet for lag" hideAll = "Skjul alle lag" showAll = "Vis alle lag" +title = "Layers" [viewer.link] delete = "Slett lenke" @@ -11646,6 +11682,7 @@ exitRedaction = "Avslutt sladdemodus" exportAll = "Eksporter PDF" exportSelected = "Eksporter valgte sider" formFill = "Fyll ut skjema" +hideToolbar = "Hide toolbar" multiTool = "Multiverktøy" panMode = "Panoreringsmodus" print = "Skriv ut PDF" @@ -11666,6 +11703,7 @@ selectAll = "Velg alle" selectByNumber = "Velg etter sidenummer" selectLanguage = "Velg språk" share = "Del" +showToolbar = "Show toolbar" toggleAnnotations = "Veksle synlighet for annoteringer" toggleAttachments = "Veksle vedlegg" toggleBookmarks = "Veksle bokmerker" diff --git a/frontend/editor/public/locales/pl-PL/translation.toml b/frontend/editor/public/locales/pl-PL/translation.toml index 60accc86f2..18568884ff 100644 --- a/frontend/editor/public/locales/pl-PL/translation.toml +++ b/frontend/editor/public/locales/pl-PL/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabet" apply = "Zastosuj" applyAndContinue = "Zapisz i wyjdź" -areYouSure = "Na pewno chcesz wyjść?" back = "Wstecz" black = "czarny" blue = "niebieski" @@ -106,6 +105,7 @@ undoSuccess = "Pomyślnie cofnięto operację" unknown = "Nieznany" unpin = "Odepnij plik (zastąp po uruchomieniu narzędzia)" unsavedChanges = "Masz niezapisane zmiany w swoim PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Niezapisane zmiany" unsupported = "Nieobsługiwane" username = "nazwa użytkownika" @@ -1879,6 +1879,9 @@ width = "Szerokość" [app] description = "Darmowa alternatywa dla Adobe Acrobat (10 mln+ pobrań)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Konwertuj do PDF/A-3b" convertToPdfA3bDescription = "Tworzy archiwalny PDF z osadzonymi załącznikami" @@ -2933,12 +2936,16 @@ text = "Aby uniemożliwić zmianę tych uprawnień, użyj narzędzia Dodaj hasł [chat.actions] copy = "Kopiuj wiadomość" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opcje agenta Stirling" +assistant = "AI Assistant" clearChat = "Wyczyść czat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Ta operacja wykorzysta Twoje kredyty w chmurze" tooltip = "Wybierz kolor z ekranu" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Wybierz kolor" [common] +actions = "Actions" back = "Wstecz" cancel = "Anuluj" close = "Zamknij" +codeSample = "Code sample" collapse = "Zwiń" confirm = "Potwierdź" continue = "Kontynuuj" copied = "Skopiowano!" copy = "Kopiuj" done = "Gotowe" +enterValue = "Enter value" error = "Błąd" expand = "Rozwiń" loading = "Wczytywanie..." next = "Dalej" +open = "Open" preview = "Podgląd" previous = "Wstecz" refresh = "Odśwież" remaining = "Pozostało" retry = "Ponów" save = "Zapisz" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Wyczyść zaznaczone" @@ -3185,6 +3199,7 @@ title = "Metoda kompresji" [compress.settings] desiredSize = "Docelowy rozmiar pliku" desiredSizePlaceholder = "Wprowadź rozmiar" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Kompresja to prosty sposób na zmniejszenie rozmiaru pliku. Wybierz File Size, aby podać docelowy rozmiar, a my dostosujemy jakość za Ciebie. Wybierz Quality, aby ręcznie ustawić siłę kompresji." @@ -3387,6 +3402,24 @@ pdfOptions = "Opcje PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Najpierw wybierz format źródłowy" settings = "Ustawienia" single = "Pojedynczy" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Szukaj..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Edytuj" @@ -3824,11 +3861,13 @@ shareSelected = "Udostępnij wybrane" sharing = "Udostępnianie" showAll = "Pokaż wszystko" showHistory = "Pokaż historię" +sortBy = "Sort files" sortByDate = "Sortuj według daty" sortByName = "Sortuj według nazwy" sortBySize = "Sortuj według rozmiaru" storage = "Magazyn" storageState = "Przechowywanie" +storageUsed = "Storage used" synced = "Zsynchronizowano" title = "Prześlij pliki PDF" toolChain = "Zastosowane narzędzia" @@ -3874,8 +3913,12 @@ addFiles = "Dodaj pliki" addingFiles = "Dodawanie plików…" collapse = "Zwiń pasek boczny" customizeGroups = "Dostosuj grupy" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Otwórz pliki, aby rozpocząć" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Rozwiń pasek boczny" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive nie jest skonfigurowany" @@ -3888,15 +3931,17 @@ openFromComputer = "Otwórz z komputera" openSettings = "Otwórz ustawienia" other = "Inne" recent = "Ostatnie" -search = "Szukaj" -searchPlaceholder = "Szukaj plików..." viewAll = "Wyświetl wszystkie pliki ({{count}})" [fileSidebar.fileItem] closeViewer = "Zamknij przeglądarkę" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Otwórz w przeglądarce" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Przywróć domyślne" subtitle = "Grupuj pliki w kategorie nadrzędne. Dodaj istniejące lub nowe etykiety do kategorii, zmień jej nazwę albo utwórz własną. Pliki spoza Twoich kategorii pojawią się w sekcji „Inne”." title = "Kategorie paska bocznego" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Dodaj do przestrzeni roboczej" addToWorkspaceCount = "Dodaj {{count}} do przestrzeni roboczej" @@ -3918,7 +3971,6 @@ backToMyFiles = "Wróć do Moich plików" breadcrumbs = "Ścieżka folderu" cancel = "Anuluj" classification = "Klasyfikacja" -clearSearch = "Wyczyść wyszukiwanie" clearSelection = "Wyczyść zaznaczenie" closeDetails = "Zamknij szczegóły" create = "Utwórz" @@ -3950,6 +4002,7 @@ downloadAll = "Pobierz wszystko" downloadVersion = "Pobierz tę wersję" dropOverlay = "Upuść pliki, aby je przesłać" dropOverlaySub = "Pliki trafiają najpierw do Lokalnych. Użyj 'Przenieś do' lub 'Zapisz w chmurze', aby umieścić je w folderze." +duplicate = "Duplicate" file = "Plik" fileInfo = "File info" fileMenu = "Akcje pliku" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Zmień rozmiar drzewa folderów (strzałki, Shift dla więks save = "Zapisz" saveToServer = "Zapisz na serwerze" saveToServerDisabledHint = "Zapisywanie na serwerze nie jest włączone na tym serwerze. Poproś administratora, aby je włączył." -search = "Szukaj" -searchPlaceholder = "Przeszukaj ten folder i podfoldery" selectAll = "Zaznacz wszystko" selectAllHint = "Kliknij, aby zaznaczyć wszystko. Wskazówka: przytrzymaj Ctrl (lub Cmd), aby dodawać pliki pojedynczo, a Shift, aby zaznaczyć zakres." selectedCount = "Wybrano: {{count}}" @@ -4044,6 +4095,10 @@ title = "Brak plików w chmurze" hint = "Pliki zapisane bez przesłania pozostają tutaj. Upuść plik, aby dodać." title = "Brak plików tylko lokalnych" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Pliki, które otwierasz lub edytujesz, pojawią się tutaj." title = "Jeszcze nic nie zmodyfikowano" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Nie można było usunąć folderu." deleteFolderFailedDetail = "Nie można było usunąć folderu: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Nie można było zaktualizować wyglądu folderu." folderAppearanceFailedDetail = "Nie można było zaktualizować wyglądu folderu: {{message}}" moveFilesFailed = "Nie można było przenieść plików." @@ -4113,6 +4170,11 @@ localHint = "Przechowywane tylko w tej przeglądarce" shared = "Udostępnione" sharedHint = "Udostępnione Tobie przez link" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Najpierw najstarsze" modifiedDesc = "Najpierw najnowsze" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Brak plików w pamięci. Najpierw otwórz jakieś pliki. open = "Otwórz" openFile = "Otwórz plik" openFiles = "Otwórz pliki" +selectFile = "Select {{name}}" selectFromStorage = "Wybierz z magazynu" upload = "Prześlij" uploadFile = "Prześlij plik" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Wszystko zapisano" +analyzingFields = "Analysing form fields..." extractCsvError = "Nie udało się wyodrębnić CSV" extractXlsxError = "Nie udało się wyodrębnić XLSX" +filled = "filled" flattenAfterFilling = "Spłaszcz po wypełnieniu" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "wym." requiredFieldsError = "Wypełnij wszystkie wymagane pola" rescanFields = "Skanuj pola ponownie" @@ -4795,9 +4863,6 @@ title = "Obraz na PDF" [imageToPdf] tags = "konwersja,img,jpg,obraz,zdjęcie" -[infoBanner] -dismiss = "Zamknij" - [invite] acceptError = "Nie udało się utworzyć konta" accountFor = "Tworzenie konta dla" @@ -4979,6 +5044,34 @@ uploadSuccess = "Przesyłanie zakończone!" uploadSuccessMessage = "Twoje obrazy zostały przesłane." validating = "Weryfikowanie sesji..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Zeskanuj, aby przesłać zdjęcia. Obrazy są automatycznie konwertowane do PDF." descriptionNoConvert = "Zeskanuj, aby przesłać zdjęcia z urządzenia mobilnego." @@ -4997,8 +5090,13 @@ title = "Prześlij z telefonu" tags = "Wielofunkcyjne narzędzie, obsługa wielu operacji, interfejs użytkownika, przeciąganie kliknięć, front-end, strona klienta" title = "Narzędzie Wielofunkcyjne PDF" -[navbar] -search = "Szukaj" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Uwierzytelnienie nie zakończyło się pomyślnie. Możesz zamknąć to okno i spróbować ponownie." @@ -5127,6 +5225,7 @@ activeFiles = "Widok Aktywne pliki pokazuje wszystkie PDF-y za allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Zamknij" cropSettings = "Teraz, gdy wybraliśmy plik, który chcemy przyciąć, możemy skonfigurować narzędzie Przytnij, aby wybrać obszar, do którego chcemy przyciąć PDF." +dialogLabel = "Onboarding" fileCheckbox = "Kliknięcie jednego z plików wybiera go do przetwarzania. Możesz wybrać wiele plików do operacji wsadowych." fileReplacement = "Zmodyfikowany plik automatycznie zastąpi oryginalny na Stole roboczym, co pozwoli łatwo przepuścić go przez kolejne narzędzia." filesButton = "Przycisk Pliki na pasku szybkiego dostępu umożliwia przesyłanie PDF-ów do użycia w narzędziach." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF na PDF/A" tags = "archiwum, długoterminowe, standardowe, konwersja, przechowywanie, konserwacja" title = "PDF na PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "druk,standard,konwersja,produkcja,prepress,archiwizacja" title = "PDF do PDF/X" @@ -6045,6 +6151,7 @@ small = "500 kredytów" xsmall = "100 kredytów" [plan.availablePlans] +currency = "Billing currency" subtitle = "Wybierz plan dopasowany do Twoich potrzeb" title = "Dostępne plany" @@ -6624,6 +6731,7 @@ reachedTitle = "Osiągnięto miesięczny limit wydatków" title = "Nie udało się otworzyć portalu Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "z {{allowance}} bezpłatnych PDF wykorzystano" capSuffix_other = "z {{allowance}} bezpłatnych PDF wykorzystano" eyebrow = "Okres próbny Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Wyślij dokument lub powiadomienie e-mailem, gdy działa zasada." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Zapisuj przetworzone dokumenty w Nextcloud." @@ -7077,11 +7211,6 @@ title = "Oficjalne SDK" beta = "Beta" deprecated = "Przestarzałe" -[portal.docs.search] -empty = "Brak pasującej dokumentacji" -placeholder = "Szukaj dokumentacji" -results = "{{count}} wyników" - [portal.docs.skills] eyebrow = "UMIEJĘTNOŚCI" lead = "Dołączone, nazwane możliwości, które agent wywołuje jako pojedyncze narzędzie. Każda umiejętność to deterministyczny łańcuch operacji z dołączonymi ewaluacjami." @@ -7180,11 +7309,11 @@ editorAction = "Edytor" empty = "Żadne dokumenty nie pasują do tego filtra." rowActions = "Działania wiersza" sensitiveLabel = "Wrażliwe" -sensitiveTitle = "Wrażliwe — wymagany dostęp" [portal.documents.table.columns] action = "Potok / Działanie" document = "Dokument" +labels = "Labels" product = "Produkt" status = "Status" time = "Czas" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Ostatnio widziano" region = "Region" status = "Status" +target = "Target" version = "Wersja" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Ten widok napotkał nieoczekiwany błąd. Spróbuj ponownie albo retry = "Spróbuj ponownie" title = "Coś poszło nie tak na tej stronie" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Wstecz" body = "Pobierz aplikację desktopową lub hostuj serwer samodzielnie." @@ -7380,11 +7560,6 @@ cancel = "Anuluj" confirm = "Unieważnij klucz" title = "Unieważnij klucz API" -[portal.infrastructure.attestationLabel] -attested = "Poświadczone" -inScope = "W zakresie" -notApplicable = "N/D" - [portal.infrastructure.audit] filterAriaLabel = "Filtruj zdarzenia audytu według kategorii" heading = "Dzienniki audytu" @@ -7456,11 +7631,6 @@ info = "Informacja" success = "Sukces" warning = "Ostrzeżenie" -[portal.infrastructure.certLabel] -certified = "Certyfikowane" -inProgress = "W toku" -notStarted = "Nie rozpoczęto" - [portal.infrastructure.createKey] cancel = "Anuluj" createKey = "Utwórz klucz" @@ -7474,238 +7644,10 @@ subtitleCreated = "Skopiuj ten sekret teraz — nie zostanie pokazany ponownie." title = "Utwórz klucz API" titleCreated = "Klucz utworzony" -[portal.infrastructure.deployLabel] -live = "Działa" -queued = "W kolejce" -rolledBack = "Wycofano" -rolling = "Wdrażanie" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Wdrożone przez" -environment = "Środowisko" -product = "Produkt" -status = "Status" -version = "Wersja" -when = "Kiedy" - -[portal.infrastructure.deployments.recent] -heading = "Ostatnie wdrożenia" -subheading = "Najnowsze wdrożenia produktów i środowisk." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instancje" -latency = "Opóźnienie" -load = "Obciążenie" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Przepustowość" -uptime = "Czas działania" -version = "Wersja" - -[portal.infrastructure.deployments.regions] -heading = "Regiony" -subheading = "Bieżąca kondycja każdego wdrożonego regionu Stirling — opóźnienie, obciążenie i wersja wdrożenia." - -[portal.infrastructure.deployments.regions.empty] -description = "Wdrożone regiony pojawią się tutaj po aprowizacji przestrzeni roboczej." -title = "Brak wdrożonych regionów" - [portal.infrastructure.keyLabel] active = "Aktywny" revoked = "Unieważniony" -[portal.infrastructure.modelLabel] -active = "Aktywny" -degraded = "Obniżona wydajność" -disabled = "Wyłączony" - -[portal.infrastructure.models] -heading = "Modele" -msValue = "{{value}} ms" -subheading = "Katalog modeli i routing, które napędzają przetwarzanie dokumentów w całej przestrzeni roboczej." - -[portal.infrastructure.models.byom] -description = "Zarejestruj model on-prem lub hostowany samodzielnie i przypnij go do regionu w celu przetwarzania związanego z rezydencją danych." -title = "Bring your own model" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Modele zarządzane dostępne w Twojej przestrzeni roboczej, z bieżącym opóźnieniem i kosztem." -subEnterprise = "Modele zarządzane, bring-your-own i on-prem — z dostępnym przypinaniem do regionów." - -[portal.infrastructure.models.catalogue.empty] -description = "Modele w katalogu Twojej przestrzeni roboczej pojawią się tutaj." -title = "Brak dostępnych modeli" - -[portal.infrastructure.models.columns] -cost = "Koszt" -latency = "Opóźnienie" -load = "Obciążenie" -model = "Model" -status = "Status" -type = "Typ" -version = "Wersja" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/wywołanie" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktywne modele" -avgLatency = "Śr. opóźnienie" -included = "Wliczone" -monthlySpend = "Miesięczne wydatki na modele" - -[portal.infrastructure.models.routing] -empty = "Nie skonfigurowano reguł routingu." -heading = "Reguły routingu" -sub = "Który model obsługuje każdą operację. Domyślny jest stosowany, gdy nie pasuje żadna węższa reguła." -subLocked = "Kieruj operacje do określonych modeli — dostępne w płatnych planach." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Ulepsz do Pro, aby kontrolować, który model obsługuje każdą operację i typ dokumentu." -title = "Routing modeli to funkcja płatna" - -[portal.infrastructure.models.routingColumns] -default = "Domyślny" -docType = "Typ dokumentu" -modelForAria = "Model dla {{operation}}" -operation = "Operacja" -routedTo = "Kierowane do" - -[portal.infrastructure.modelTypeLabel] -classification = "Klasyfikacja" -extraction = "Ekstrakcja" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Obniżona wydajność" -down = "Niedostępny" -healthy = "Sprawny" - -[portal.infrastructure.security.access.byok] -description = "Dostarcz klucz z własnego KMS. Stirling szyfruje nim, ale nadal może odczytywać." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Klucze nigdy nie opuszczają Twojego KMS. Stirling przechowuje tylko szyfrogram." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling zarządza kluczami szyfrowania. Najprostsze — zero operacji na kluczach po Twojej stronie." -label = "Klucze przechowywane przez Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Zasada dostępu do dokumentów" -subheading = "Kontroluje, kto może odszyfrować przetworzone dokumenty w spoczynku." - -[portal.infrastructure.security.attestations] -heading = "Atestacje zgodności" -noReport = "Brak dostępnego raportu" -subheading = "Stan audytu według frameworków, z raportami dostępnymi dla poświadczonych kontroli." -viewReport = "Wyświetl raport →" - -[portal.infrastructure.security.compliance] -heading = "Zgodność" -subheading = "Atestacje i certyfikaty obejmujące platformę Stirling." - -[portal.infrastructure.security.empty] -description = "Konfiguracja bezpieczeństwa Twojej przestrzeni roboczej pojawi się tutaj." -title = "Stan bezpieczeństwa niedostępny" - -[portal.infrastructure.security.hyokBanner] -description = "Dzięki HYOK klucze szyfrowania nigdy nie opuszczają Twojego KMS. Stirling przechowuje i przetwarza tylko szyfrogram, który możesz w każdej chwili unieważnić." -title = "Stirling nie może odszyfrować Twoich dokumentów" - -[portal.infrastructure.security.ipAllowlist] -empty = "Nie skonfigurowano zakresów IP — wszystkie IP są dozwolone." -heading = "Lista dozwolonych IP" -sub = "Dostęp do API jest ograniczony do tych zakresów CIDR." -subLocked = "Ogranicz dostęp do API do znanych zakresów IP — dostępne w płatnych planach." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Ulepsz do Pro, aby ograniczyć dostęp do API do określonych sieci." -title = "Lista dozwolonych IP to funkcja płatna" - -[portal.infrastructure.security.ipColumns] -added = "Dodano" -addedBy = "Dodane przez" -cidr = "CIDR" -label = "Etykieta" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorytm" -heading = "Zarządzanie kluczami szyfrowania" -keyId = "Identyfikator klucza" -lastRotated = "Ostatnio rotowano" -rotateKey = "Rotuj klucz" -rotationPolicy = "Zasada rotacji" -subheading = "Opieka nad kluczami szyfrującymi dokumenty w spoczynku — kto może odszyfrować i jak rotują klucze." - -[portal.infrastructure.security.managedBanner] -description = "Opieka bring-your-own-key (BYOK) i hold-your-own-key (HYOK) jest dostępna w Enterprise. Ulepsz, aby dostarczać klucze z własnego KMS." -title = "Klucze są zarządzane przez Stirling w Twoim planie" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Azja i Pacyfik" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · granica danych GDPR" -label = "Unia Europejska" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Stany Zjednoczone" - -[portal.infrastructure.security.residencyHeader] -heading = "Rezydencja danych" -subheading = "Gdzie dokumenty są przechowywane i przetwarzane." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} użyte" - -[portal.infrastructure.storage.empty] -description = "Połączony magazyn i użycie pojawią się tutaj." -title = "Brak skonfigurowanego magazynu" - -[portal.infrastructure.storage.lifecycle] -active = "Aktywne" -activeRange = "0–{{value}}d" -archived = "Zarchiwizowane" -coldStorage = "cold storage" -deleted = "Usunięte" -never = "nigdy" -purged = "wyczyszczone" - -[portal.infrastructure.storage.providers] -connect = "Połącz" -connected = "Połączono" -heading = "Połączeni dostawcy" -subheading = "Gdzie zapisywane są przetworzone artefakty." - -[portal.infrastructure.storage.retention] -heading = "Retencja" -subheading = "Jak długo artefakty są przechowywane przed usunięciem w cyklu życia." -windowLabel = "Domyślne okno retencji" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dzień" -days_other = "{{count}} dni" -never = "Nigdy nie usuwaj" - -[portal.infrastructure.storage.totalUsage] -heading = "Łączne użycie" -progressLabel = "Użyty magazyn" -subheading = "Magazyn wykorzystany we wszystkich połączonych dostawcach." - [portal.infrastructure.tabs] apiKeys = "Klucze API" audit = "Dzienniki audytu" @@ -7715,11 +7657,9 @@ security = "Bezpieczeństwo" storage = "Magazyn" [portal.integrations] -addAnother = "Dodaj kolejną" availableHeading = "Dostępne" comingSoonHeading = "Wkrótce" connect = "Połącz" -connectedHeading = "Połączone" connectionCount_one = "{{count}} połączenie" connectionCount_other = "{{count}} połączeń" customApi = "Niestandardowe API" @@ -7737,6 +7677,10 @@ security = "Bezpieczeństwo" signing = "Podpisywanie" storage = "Magazyn" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Połączono" @@ -7767,7 +7711,6 @@ integrations = "Integracje" pipelines = "Potoki" policies = "Zasady" procurement = "Zakupy" -settings = "Ustawienia" sources = "Źródła" usage = "Użycie i rozliczenia" users = "Użytkownicy" @@ -7784,7 +7727,7 @@ title = "Potoki" newPipeline = "Nowy potok" [portal.pipelines.builder] -addStep = "Dodaj narzędzie" +activate = "Activate" back = "Wróć do potoków" cannotFollow = "Nie może przyjąć {{produced}}" chooseAccount = "Wybierz konto" @@ -7792,28 +7735,38 @@ chooseDestination = "Wybierz miejsce docelowe" chooseOperation = "Wybierz, co robi ten krok" chooseSource = "Wybierz źródło" discard = "Odrzuć zmiany" -enabled = "Włączone" inputs = "Wejście" inputSource = "Źródło wejściowe" inputTrigger = "Wyzwalacz" keepEditing = "Kontynuuj edycję" +moreActions = "More actions" needsConfiguring = "Wymaga konfiguracji" -needsUpload = "Wymaga przesłanego pliku" -noSources = "Nie połączono jeszcze żadnych źródeł. Utwórz źródło, aby użyć go jako wejścia." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Żadne narzędzia nie pasują do wyszukiwania." -pipelineSettings = "Ustawienia potoku" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Szukaj narzędzi" -selectToolBody = "Dodaj narzędzie, aby zbudować potok." -selectToolTitle = "Brak narzędzi" sendToSystem = "Wyślij do innego systemu" stepsIncompatible = "Te kroki nie mogą działać na tym, co wytwarza poprzedni krok: {{tools}}." stepsNeedSetup = "Te kroki nadal wymagają konfiguracji przed zapisaniem: {{tools}}." -toolSettings = "Ustawienia narzędzia" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Nierozpoznana operacja, zachowana bez zmian." unsavedBody = "Masz niezapisane zmiany. Zapisać je przed opuszczeniem czy odrzucić?" unsavedTitle = "Niezapisane zmiany" -uploadUnsupported = "Przesłane pliki nie są jeszcze obsługiwane w potokach, więc tych kroków nie można zapisać: {{tools}}." usesDefaults = "Uruchamia się z ustawieniami domyślnymi" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Łączy każdy plik z poprzedniego kroku" @@ -7825,31 +7778,36 @@ undeclared-operation = "Nie można sprawdzić, co akceptuje ten krok" [portal.pipelines.composer] addTool = "Dodaj narzędzie" -cancel = "Anuluj" -chainEmpty = "Dodaj narzędzie, aby rozpocząć budowanie potoku." create = "Utwórz potok" +createPaused = "Create paused" editingUnsupported = "Wyświetlanie tych parametrów narzędzia do edycji nie jest jeszcze obsługiwane." -moveDown = "Przenieś w dół" -moveUp = "Przenieś w górę" +editSource = "Edit source" name = "Nazwa" namePlaceholder = "np. Przegląd redakcji" noToolSettings = "To narzędzie nie ma konfigurowalnych ustawień." -operations_one = "Operacja ({{count}})" -operations_other = "Operacje ({{count}})" output = "Wyjście" -removeStep = "Usuń operację" save = "Zapisz zmiany" scheduleEvery = "Uruchamiaj co" -sources = "Źródła" -sourcesLoading = "Ładowanie źródeł..." trigger = "Wyzwalacz" triggerManual = "Tylko ręcznie" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dni" hours = "godzin" minutes = "minut" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Usunąć \"{{name}}\"? Tego nie można cofnąć." cancel = "Anuluj" @@ -7867,6 +7825,37 @@ connectSource = "Połącz źródło" description = "Utwórz swój pierwszy potok: wybierz źródła, na których działa, połącz operacje w łańcuch i wybierz miejsce zapisu wyjścia." title = "Brak potoków" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktywne" paused = "Wstrzymane" @@ -7879,7 +7868,6 @@ completed_one = "Uruchomienie zakończone." completed_other = "Wszystkie {{count}} uruchomienia zakończone." empty = "Nie ma nic do uruchomienia: źródła nie miały dokumentów do przetworzenia." failed = "Uruchomienie nie powiodło się: {{error}}" -historyCleared = "Historia wyczyszczona. Następne uruchomienie ponownie przetworzy wszystko, co obecnie znajduje się w źródłach." inFlight = "Nic nowego do uruchomienia: dokumenty są nadal przetwarzane z wcześniejszego uruchomienia." parked_one = "Nic do uruchomienia: {{count}} dokument wcześniej zakończył się niepowodzeniem i jest zaparkowany. Usuń przyczynę, a następnie wyczyść historię, aby spróbować ponownie." parked_other = "Nic do uruchomienia: {{count}} dokumentów wcześniej zakończyło się niepowodzeniem i jest zaparkowanych. Usuń przyczynę, a następnie wyczyść historię, aby spróbować ponownie." @@ -7895,6 +7883,7 @@ name = "Potok" sources = "Źródła" status = "Status" steps = "Kroki" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Obserwacja folderu" @@ -8209,6 +8198,24 @@ label = "Dołącz do zgłoszenia Jira" description = "Wysyła przetworzony dokument jako załącznik." label = "Wyślij dokument emailem (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Zapisuje przetworzony dokument w folderze." label = "Prześlij do Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Limit wyczerpany" subtitle = "Twoja bezpłatna wersja próbna działa do {{date}}. Karta nie jest wymagana." title = "Wersja próbna Enterprise" -[portal.search] -ariaLabel = "Szukaj" -placeholder = "Szukaj w Stirling — endpointy, potoki, dokumenty…" - -[portal.search.empty] -noActionsDescription = "Szybkie działania pojawią się tutaj, gdy będą dostępne." -noActionsTitle = "Brak szybkich działań" -noMatches = "Brak wyników dla „{{query}}”" -noMatchesDescription = "Spróbuj innego słowa kluczowego lub przejrzyj katalog." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Anuluj" confirm = "Usuń" title = "Usunąć źródło?" -[portal.sources.empty] -description = "Połącz folder (a wkrótce także magazyn w chmurze), aby Twoje polityki miały skąd pobierać dokumenty." -title = "Nie połączono jeszcze żadnych źródeł" - [portal.sources.kpi] inUse = "W użyciu" total = "Połączenia" @@ -8785,6 +8778,7 @@ unused = "Nieużywane" documents = "Dokumenty" source = "Źródło" status = "Status" +type = "Type" usedBy = "Polityki" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Zasady i prywatność" [settings.preferences] title = "Preferencje" -[settings.search] -placeholder = "Szukaj stron ustawień..." - [settings.security] description = "Zmień hasło, aby zachować bezpieczeństwo konta." title = "Bezpieczeństwo" @@ -10128,6 +10119,7 @@ last = "Ostatnia strona" maintainRatio = "Przełącz zachowanie proporcji" next = "Następna strona" noSavedSigs = "Nie znaleziono zapisanych podpisów" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Podpisy osobiste" previous = "Poprzednia strona" redo = "Ponów" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Nie udało się usunąć tła" hint = "Prześlij obraz podpisu w formacie PNG lub JPG" label = "Prześlij obraz podpisu" placeholder = "Wybierz plik obrazu" +previewAlt = "Current image signature" processing = "Przetwarzanie obrazu..." removeBackground = "Usuń białe tło (uczyń przezroczystym)" @@ -10179,6 +10172,17 @@ saved = "Wybierz zapisany podpis powyżej, a następnie kliknij w dowolnym miejs text = "Po wpisaniu swojego imienia i nazwiska kliknij w dowolnym miejscu PDF, aby umieścić podpis." title = "Jak dodać podpis" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Przesuń podpis" pause = "Wstrzymaj umieszczanie" @@ -10625,6 +10629,23 @@ title = "Prześlij na serwer" updateButton = "Zaktualizuj na serwerze" uploadButton = "Prześlij na serwer" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Wróć" pdfTools = "Narzędzia PDF" placeholder = "Wybierz narzędzie, aby zacząć" premiumFeature = "Funkcja premium:" -searchTools = "Szukaj narzędzi" toolsHeader = "Narzędzia" viewAllTools = "Wyświetl wszystkie narzędzia" @@ -10822,6 +10842,9 @@ removal = "Usuwanie" signing = "Podpisywanie" verification = "Weryfikacja" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nie znaleziono narzędzi" noTools = "Brak dostępnych narzędzi" @@ -10979,6 +11002,13 @@ approver = "Zatwierdza politykę" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Anulować zaproszenie dla {{email}}? Ta osoba nie będzie mogła dołączyć za pomocą bieżącego linku." cancelInviteTitle = "Anuluj zaproszenie" @@ -10998,10 +11028,8 @@ title = "Brak członków" addToTeam = "Dodaj do zespołu" guestCount = "{{count}} gość" guests = "Goście" -guestsDesc = "Zewnętrzni współpracownicy, ograniczeni do tego, co udostępniono. Tylko Editor." ledBy = "prowadzony przez {{owner}}" org = "Organizacja" -orgDesc = "Właściciele z uprawnieniami w całej organizacji i zatwierdzaniem polityk" owners = "{{count}} właściciel" team = "Zespół {{name}}" teamMeta = "{{count}} osób" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Zaprosił(a): {{who}}" cancel = "Anuluj" -count = "{{count}} oczekujących" -desc = "Zaproszone osoby, które jeszcze nie dołączyły. Zajmują miejsce, dopóki nie zaakceptują zaproszenia." expiresInDays_one = "Wygasa za {{count}} dzień" expiresInDays_other = "Wygasa za {{count}} dni" expiresToday = "Wygasa dzisiaj" title = "Oczekujące zaproszenia" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Coś poszło nie tak podczas łączenia z backendem albo nie masz dostępu. Spróbuj ponownie." title = "Nie udało się załadować członków" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Szukaj w załącznikach" title = "Załączniki" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Tytuł zakładki" bookmarkTitleRequired = "Tytuł zakładki jest wymagany" closeSidebar = "Zamknij panel boczny zakładek" collapseAll = "Zwiń wszystkie zakładki" +empty = "No bookmarks in this document" expandAll = "Rozwiń wszystkie zakładki" searchPlaceholder = "Szukaj zakładek" +title = "Bookmarks" [viewer.comments] addComment = "Dodaj komentarz" @@ -11264,10 +11297,12 @@ locateAnnotation = "Zlokalizuj w dokumencie" moreActions = "Więcej działań" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Strona {{page}}" placingHint = "Kliknij stronę, aby umieścić… (anuluj)" removeCommentOnly = "Usuń tylko komentarz" saveReply = "Zapisz odpowiedź" +searchPlaceholder = "Search comments" title = "Komentarze" typeComment = "Komentarz" typeInsertText = "Wstaw tekst" @@ -11290,6 +11325,7 @@ unsavedDesc = "Masz niezapisane zmiany" closeSidebar = "Zamknij panel boczny warstw" hideAll = "Ukryj wszystkie warstwy" showAll = "Pokaż wszystkie warstwy" +title = "Layers" [viewer.link] delete = "Usuń link" @@ -11646,6 +11682,7 @@ exitRedaction = "Zakończ tryb zaczerniania" exportAll = "Eksportuj PDF" exportSelected = "Eksportuj wybrane strony" formFill = "Wypełnij formularz" +hideToolbar = "Hide toolbar" multiTool = "Wielofunkcyjne narzędzie" panMode = "Tryb przesuwania" print = "Drukuj PDF" @@ -11666,6 +11703,7 @@ selectAll = "Zaznacz wszystko" selectByNumber = "Zaznacz według numerów stron" selectLanguage = "Wybierz język" share = "Udostępnij" +showToolbar = "Show toolbar" toggleAnnotations = "Pokaż/ukryj adnotacje" toggleAttachments = "Pokaż/ukryj załączniki" toggleBookmarks = "Pokaż/ukryj zakładki" diff --git a/frontend/editor/public/locales/pt-BR/translation.toml b/frontend/editor/public/locales/pt-BR/translation.toml index e725bf5682..b897248eb8 100644 --- a/frontend/editor/public/locales/pt-BR/translation.toml +++ b/frontend/editor/public/locales/pt-BR/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabeto" apply = "Aplicar" applyAndContinue = "Aplicar e continuar" -areYouSure = "Tem certeza de que deseja sair?" back = "Voltar" black = "Preto" blue = "Azul" @@ -106,6 +105,7 @@ undoSuccess = "Operação desfeita com sucesso" unknown = "Desconhecido" unpin = "Desafixar arquivo (substituir após execução da ferramenta)" unsavedChanges = "Você tem alterações não salvas no seu PDF. O que você gostaria de fazer?" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Alterações não salvas" unsupported = "Não suportado" username = "Usuário" @@ -1879,6 +1879,9 @@ width = "Largura" [app] description = "A alternativa gratuita ao Adobe Acrobat (10M+ downloads)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Converter para PDF/A-3b" convertToPdfA3bDescription = "Cria um PDF de arquivamento com anexos incorporados" @@ -2933,12 +2936,16 @@ text = "Para tornar essas permissões imutáveis, use a ferramenta Adicionar sen [chat.actions] copy = "Copiar mensagem" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opções do agente Stirling" +assistant = "AI Assistant" clearChat = "Limpar chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Esta operação usará seus créditos de nuvem" tooltip = "Selecionar cor da tela" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Escolha a cor" [common] +actions = "Actions" back = "Voltar" cancel = "Cancelar" close = "Fechar" +codeSample = "Code sample" collapse = "Recolher" confirm = "Confirmar" continue = "Continuar" copied = "Copiado!" copy = "Copiar" done = "Concluído" +enterValue = "Enter value" error = "Erro" expand = "Expandir" loading = "Carregando..." next = "Próximo" +open = "Open" preview = "Pré-visualização" previous = "Anterior" refresh = "Atualizar" remaining = "Restante" retry = "Tentar novamente" save = "Salvar" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Limpar selecionados" @@ -3185,6 +3199,7 @@ title = "Método de compressão" [compress.settings] desiredSize = "Tamanho de arquivo desejado" desiredSizePlaceholder = "Insira o tamanho" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "A compressão é uma forma fácil de reduzir o tamanho do arquivo. Escolha Tamanho do arquivo para informar um tamanho alvo e deixarmos a qualidade ajustada para você. Escolha Qualidade para definir manualmente a intensidade da compressão." @@ -3387,6 +3402,24 @@ pdfOptions = "Opções de PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Selecione primeiro um formato de origem" settings = "Configurações" single = "Único" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Pesquisar..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Editar" @@ -3824,11 +3861,13 @@ shareSelected = "Compartilhar selecionados" sharing = "Compartilhamento" showAll = "Mostrar tudo" showHistory = "Mostrar histórico" +sortBy = "Sort files" sortByDate = "Ordenar por data" sortByName = "Ordenar por nome" sortBySize = "Ordenar por tamanho" storage = "Armazenamento" storageState = "Armazenamento" +storageUsed = "Storage used" synced = "Sincronizado" title = "Fazer upload de arquivos PDF" toolChain = "Ferramentas aplicadas" @@ -3874,8 +3913,12 @@ addFiles = "Adicionar arquivos" addingFiles = "Adicionando arquivos…" collapse = "Recolher barra lateral" customizeGroups = "Personalizar grupos" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Abra arquivos para começar" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Expandir barra lateral" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive não está configurado" @@ -3888,15 +3931,17 @@ openFromComputer = "Abrir do computador" openSettings = "Abrir configurações" other = "Outro" recent = "Recentes" -search = "Pesquisar" -searchPlaceholder = "Pesquisar arquivos..." viewAll = "Ver todos os {{count}} arquivos" [fileSidebar.fileItem] closeViewer = "Fechar visualizador" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Abrir no visualizador" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Redefinir para os padrões" subtitle = "Agrupe seus arquivos em categorias principais. Adicione rótulos existentes ou novos a uma categoria, renomeie-a ou crie a sua própria. Arquivos que não estão em nenhuma das suas categorias aparecem em “Outro”." title = "Categorias da barra lateral" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Adicionar ao espaço de trabalho" addToWorkspaceCount = "Adicionar {{count}} ao espaço de trabalho" @@ -3918,7 +3971,6 @@ backToMyFiles = "Voltar para Meus arquivos" breadcrumbs = "Caminho da pasta" cancel = "Cancelar" classification = "Classificação" -clearSearch = "Limpar busca" clearSelection = "Limpar seleção" closeDetails = "Fechar detalhes" create = "Criar" @@ -3950,6 +4002,7 @@ downloadAll = "Baixar tudo" downloadVersion = "Baixar esta versão" dropOverlay = "Solte arquivos para enviar" dropOverlaySub = "Os arquivos começam em Local. Use 'Move to' ou 'Save to cloud' para organizá-los em uma pasta." +duplicate = "Duplicate" file = "Arquivo" fileInfo = "File info" fileMenu = "Ações do arquivo" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Redimensionar árvore de pastas (setas, Shift para passos ma save = "Salvar" saveToServer = "Salvar no servidor" saveToServerDisabledHint = "Salvar no servidor não está habilitado neste servidor. Peça ao seu administrador para habilitar." -search = "Pesquisar" -searchPlaceholder = "Pesquisar nesta pasta e subpastas" selectAll = "Selecionar tudo" selectAllHint = "Clique para selecionar tudo. Dica: mantenha Ctrl (ou Cmd) para adicionar arquivos um a um, e Shift para selecionar um intervalo." selectedCount = "{{count}} selecionado(s)" @@ -4044,6 +4095,10 @@ title = "Ainda não há arquivos na nuvem" hint = "Arquivos salvos sem enviar ficam aqui. Solte um arquivo para adicionar." title = "Nenhum arquivo somente local" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Arquivos que você abrir ou editar aparecerão aqui." title = "Nada modificado ainda" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Não foi possível excluir a pasta." deleteFolderFailedDetail = "Não foi possível excluir a pasta: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Não foi possível atualizar a aparência da pasta." folderAppearanceFailedDetail = "Não foi possível atualizar a aparência da pasta: {{message}}" moveFilesFailed = "Não foi possível mover os arquivos." @@ -4113,6 +4170,11 @@ localHint = "Armazenado apenas neste navegador" shared = "Compartilhado" sharedHint = "Compartilhado com você via link" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Mais antigo primeiro" modifiedDesc = "Mais recente primeiro" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Nenhum arquivo disponível no armazenamento. Abra alguns open = "Abrir" openFile = "Abrir arquivo" openFiles = "Abrir arquivos" +selectFile = "Select {{name}}" selectFromStorage = "Selecionar do armazenamento" upload = "Fazer upload" uploadFile = "Fazer upload de arquivo" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Tudo salvo" +analyzingFields = "Analysing form fields..." extractCsvError = "Falha ao extrair CSV" extractXlsxError = "Falha ao extrair XLSX" +filled = "filled" flattenAfterFilling = "Achatar após o preenchimento" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obr" requiredFieldsError = "Preencha todos os campos obrigatórios" rescanFields = "Escanear campos novamente" @@ -4795,9 +4863,6 @@ title = "Imagem para PDF" [imageToPdf] tags = "conversão,img,jpg,imagem,foto" -[infoBanner] -dismiss = "Dispensar" - [invite] acceptError = "Falha ao criar conta" accountFor = "Criando conta para" @@ -4979,6 +5044,34 @@ uploadSuccess = "Envio concluído!" uploadSuccessMessage = "Suas imagens foram transferidas." validating = "Validando sessão..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Escaneie para enviar fotos. As imagens convertem automaticamente para PDF." descriptionNoConvert = "Escaneie para enviar fotos do seu dispositivo móvel." @@ -4997,8 +5090,13 @@ title = "Enviar do celular" tags = "Multiferramentas,múltiplas operações,Interface do Usuário,Clique e arraste,front-end,lado do cliente,interativo,intratável,movimento,excluir,migrar,dividir" title = "Multiferramentas de PDF" -[navbar] -search = "Pesquisar" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "A autenticação não foi bem-sucedida. Você pode fechar esta janela e tentar novamente." @@ -5127,6 +5225,7 @@ activeFiles = "A visualização de Arquivos Ativos mostra todos allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Fechar" cropSettings = "Agora que selecionamos o arquivo que queremos recortar, podemos configurar a ferramenta Recortar para escolher a área para a qual queremos recortar o PDF." +dialogLabel = "Onboarding" fileCheckbox = "Clicar em um dos arquivos o seleciona para processamento. Você pode selecionar vários arquivos para operações em lote." fileReplacement = "O arquivo modificado substituirá automaticamente o arquivo original no Workbench, permitindo que você o passe facilmente por mais ferramentas." filesButton = "O botão Arquivos na barra de Acesso Rápido permite fazer upload de PDFs para usar nas ferramentas." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF para PDF/A" tags = "arquivo,longo prazo,padrão,conversão,armazenamento,preservação" title = "PDF para PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "impressão,padrão,conversão,produção,pré-impressão,arquivo" title = "PDF para PDF/X" @@ -6045,6 +6151,7 @@ small = "500 créditos" xsmall = "100 créditos" [plan.availablePlans] +currency = "Billing currency" subtitle = "Escolha o plano que atende às suas necessidades" title = "Planos disponíveis" @@ -6624,6 +6731,7 @@ reachedTitle = "Limite de gastos mensal atingido" title = "Não foi possível abrir o portal do Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "de {{allowance}} PDFs gratuitos usados" capSuffix_other = "de {{allowance}} PDFs gratuitos usados" eyebrow = "Teste do Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Envie um documento ou uma notificação por email quando uma política for executada." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Arquive documentos processados no Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDKs oficiais" beta = "Beta" deprecated = "Obsoleto" -[portal.docs.search] -empty = "Nenhum doc correspondente" -placeholder = "Buscar docs" -results = "{{count}} resultados" - [portal.docs.skills] eyebrow = "HABILIDADES" lead = "Capacidades agrupadas e nomeadas que seu agente invoca como uma única ferramenta. Cada habilidade é uma cadeia determinística de operações com avaliações anexadas." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Nenhum documento corresponde a este filtro." rowActions = "Ações da linha" sensitiveLabel = "Sensível" -sensitiveTitle = "Sensível — acesso necessário" [portal.documents.table.columns] action = "Pipeline / Ação" document = "Documento" +labels = "Labels" product = "Produto" status = "Status" time = "Hora" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Visto por último" region = "Região" status = "Status" +target = "Target" version = "Versão" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Esta visualização encontrou um erro inesperado. Tente novamente retry = "Tentar novamente" title = "Algo deu errado nesta página" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Voltar" body = "Baixe o app para desktop ou auto-hospede o servidor." @@ -7380,11 +7560,6 @@ cancel = "Cancelar" confirm = "Revogar chave" title = "Revogar chave API" -[portal.infrastructure.attestationLabel] -attested = "Atestado" -inScope = "No escopo" -notApplicable = "N/D" - [portal.infrastructure.audit] filterAriaLabel = "Filtrar eventos de auditoria por categoria" heading = "Logs de auditoria" @@ -7456,11 +7631,6 @@ info = "Info" success = "Sucesso" warning = "Aviso" -[portal.infrastructure.certLabel] -certified = "Certificado" -inProgress = "Em andamento" -notStarted = "Não iniciado" - [portal.infrastructure.createKey] cancel = "Cancelar" createKey = "Criar chave" @@ -7474,238 +7644,10 @@ subtitleCreated = "Copie este segredo agora — ele não será exibido novamente title = "Criar chave de API" titleCreated = "Chave criada" -[portal.infrastructure.deployLabel] -live = "No ar" -queued = "Na fila" -rolledBack = "Revertido" -rolling = "Em implantação" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Implantado por" -environment = "Ambiente" -product = "Produto" -status = "Status" -version = "Versão" -when = "Quando" - -[portal.infrastructure.deployments.recent] -heading = "Implantações recentes" -subheading = "Os lançamentos mais recentes entre produtos e ambientes." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instâncias" -latency = "Latência" -load = "Carga" -p99 = "P99" -region = "Região" -status = "Status" -throughput = "Throughput" -uptime = "Uptime" -version = "Versão" - -[portal.infrastructure.deployments.regions] -heading = "Regiões" -subheading = "Integridade ao vivo de cada região Stirling implantada — latência, carga e versão de rollout." - -[portal.infrastructure.deployments.regions.empty] -description = "As regiões implantadas aparecerão aqui assim que seu workspace for provisionado." -title = "Nenhuma região implantada" - [portal.infrastructure.keyLabel] active = "Ativo" revoked = "Revogado" -[portal.infrastructure.modelLabel] -active = "Ativo" -degraded = "Degradado" -disabled = "Desativado" - -[portal.infrastructure.models] -heading = "Modelos" -msValue = "{{value}} ms" -subheading = "O catálogo de modelos e roteamento que impulsiona o processamento de documentos em todo o seu workspace." - -[portal.infrastructure.models.byom] -description = "Registre um modelo on-prem ou auto-hospedado e fixe-o a uma região para processamento vinculado à residência de dados." -title = "Traga seu próprio modelo" - -[portal.infrastructure.models.catalogue] -heading = "Catálogo" -sub = "Modelos gerenciados disponíveis para seu workspace, com latência e custo ao vivo." -subEnterprise = "Modelos gerenciados, próprios e on-prem — com fixação por região disponível." - -[portal.infrastructure.models.catalogue.empty] -description = "Os modelos no catálogo do seu workspace aparecem aqui." -title = "Nenhum modelo disponível" - -[portal.infrastructure.models.columns] -cost = "Custo" -latency = "Latência" -load = "Carga" -model = "Modelo" -status = "Status" -type = "Tipo" -version = "Versão" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/chamada" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Modelos ativos" -avgLatency = "Latência média" -included = "Incluído" -monthlySpend = "Gasto mensal com modelos" - -[portal.infrastructure.models.routing] -empty = "Nenhuma regra de roteamento configurada." -heading = "Regras de roteamento" -sub = "Qual modelo lida com cada operação. O padrão se aplica quando nenhuma regra mais específica corresponde." -subLocked = "Direcione operações para modelos específicos — disponível em planos pagos." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Atualize para Pro para controlar qual modelo lida com cada operação e tipo de documento." -title = "Roteamento de modelos é um recurso pago" - -[portal.infrastructure.models.routingColumns] -default = "Padrão" -docType = "Tipo de documento" -modelForAria = "Modelo para {{operation}}" -operation = "Operação" -routedTo = "Roteado para" - -[portal.infrastructure.modelTypeLabel] -classification = "Classificação" -extraction = "Extração" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degradado" -down = "Indisponível" -healthy = "Íntegro" - -[portal.infrastructure.security.access.byok] -description = "Forneça uma chave do seu próprio KMS. O Stirling criptografa com ela, mas ainda consegue ler." -label = "Traga sua própria chave (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "As chaves nunca saem do seu KMS. O Stirling mantém apenas ciphertext." -label = "Mantenha sua própria chave (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "O Stirling gerencia as chaves de criptografia. O mais simples — zero operações de chave do seu lado." -label = "Chaves mantidas pelo Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Política de acesso a documentos" -subheading = "Controla quem pode descriptografar documentos processados em repouso." - -[portal.infrastructure.security.attestations] -heading = "Atestados de conformidade" -noReport = "Nenhum relatório disponível" -subheading = "Postura de auditoria framework por framework, com relatórios disponíveis para controles atestados." -viewReport = "Ver relatório →" - -[portal.infrastructure.security.compliance] -heading = "Conformidade" -subheading = "Atestados e certificações que cobrem a plataforma Stirling." - -[portal.infrastructure.security.empty] -description = "A configuração de segurança do seu workspace aparecerá aqui." -title = "Postura de segurança indisponível" - -[portal.infrastructure.security.hyokBanner] -description = "Com HYOK, as chaves de criptografia nunca saem do seu KMS. O Stirling armazena e processa apenas ciphertext que você pode revogar a qualquer momento." -title = "O Stirling não consegue descriptografar seus documentos" - -[portal.infrastructure.security.ipAllowlist] -empty = "Nenhum intervalo de IP configurado — todos os IPs permitidos." -heading = "Allowlist de IP" -sub = "O acesso à API é restrito a estes intervalos CIDR." -subLocked = "Restrinja o acesso à API a intervalos de IP conhecidos — disponível em planos pagos." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Atualize para Pro para restringir o acesso à API a redes específicas." -title = "Allowlist de IP é um recurso pago" - -[portal.infrastructure.security.ipColumns] -added = "Adicionado" -addedBy = "Adicionado por" -cidr = "CIDR" -label = "Rótulo" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritmo" -heading = "Gerenciamento de chaves de criptografia" -keyId = "Identificador da chave" -lastRotated = "Rotacionada pela última vez" -rotateKey = "Rotacionar chave" -rotationPolicy = "Política de rotação" -subheading = "Custódia das chaves que criptografam documentos em repouso — quem pode descriptografar e como as chaves são rotacionadas." - -[portal.infrastructure.security.managedBanner] -description = "Custódia bring-your-own-key (BYOK) e hold-your-own-key (HYOK) estão disponíveis no Enterprise. Atualize para fornecer chaves do seu próprio KMS." -title = "As chaves são gerenciadas pelo Stirling no seu plano" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Ásia-Pacífico" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · limite de dados GDPR" -label = "União Europeia" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Estados Unidos" - -[portal.infrastructure.security.residencyHeader] -heading = "Residência de dados" -subheading = "Onde os documentos são armazenados e processados." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} usado" - -[portal.infrastructure.storage.empty] -description = "Armazenamento conectado e uso aparecem aqui." -title = "Nenhum armazenamento configurado" - -[portal.infrastructure.storage.lifecycle] -active = "Ativo" -activeRange = "0–{{value}}d" -archived = "Arquivado" -coldStorage = "cold storage" -deleted = "Excluído" -never = "nunca" -purged = "expurgado" - -[portal.infrastructure.storage.providers] -connect = "Conectar" -connected = "Conectado" -heading = "Provedores conectados" -subheading = "Onde os artefatos processados são gravados." - -[portal.infrastructure.storage.retention] -heading = "Retenção" -subheading = "Por quanto tempo os artefatos são mantidos antes da exclusão do ciclo de vida." -windowLabel = "Janela de retenção padrão" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dia" -days_other = "{{count}} dias" -never = "Nunca excluir" - -[portal.infrastructure.storage.totalUsage] -heading = "Uso total" -progressLabel = "Armazenamento usado" -subheading = "Armazenamento consumido em todos os provedores conectados." - [portal.infrastructure.tabs] apiKeys = "Chaves de API" audit = "Logs de auditoria" @@ -7715,11 +7657,9 @@ security = "Segurança" storage = "Armazenamento" [portal.integrations] -addAnother = "Adicionar outro" availableHeading = "Disponíveis" comingSoonHeading = "Em breve" connect = "Conectar" -connectedHeading = "Conectadas" connectionCount_one = "{{count}} conexão" connectionCount_other = "{{count}} conexões" customApi = "API personalizada" @@ -7737,6 +7677,10 @@ security = "Segurança" signing = "Assinatura" storage = "Armazenamento" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Conectado" @@ -7767,7 +7711,6 @@ integrations = "Integrações" pipelines = "Pipelines" policies = "Políticas" procurement = "Compras" -settings = "Configurações" sources = "Fontes" usage = "Uso e faturamento" users = "Usuários" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Novo pipeline" [portal.pipelines.builder] -addStep = "Adicionar ferramenta" +activate = "Activate" back = "Voltar para pipelines" cannotFollow = "Não é possível receber {{produced}}" chooseAccount = "Escolha uma conta" @@ -7792,28 +7735,38 @@ chooseDestination = "Escolha um destino" chooseOperation = "Escolha o que esta etapa faz" chooseSource = "Escolha uma fonte" discard = "Descartar alterações" -enabled = "Ativado" inputs = "Entrada" inputSource = "Fonte de entrada" inputTrigger = "Acionador" keepEditing = "Continuar editando" +moreActions = "More actions" needsConfiguring = "Precisa ser configurado" -needsUpload = "Precisa de um arquivo enviado" -noSources = "Nenhuma fonte conectada ainda. Crie uma para usá-la como entrada." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Nenhuma ferramenta corresponde à sua busca." -pipelineSettings = "Configurações do pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Buscar ferramentas" -selectToolBody = "Adicione uma ferramenta para criar seu pipeline." -selectToolTitle = "Ainda não há ferramentas" sendToSystem = "Enviar para outro sistema" stepsIncompatible = "Estas etapas não podem ser executadas no que a etapa anterior produz: {{tools}}." stepsNeedSetup = "Estas etapas ainda precisam ser configuradas antes de salvar: {{tools}}." -toolSettings = "Configurações da ferramenta" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Operação não reconhecida, mantida como está." unsavedBody = "Você tem alterações não salvas. Salve-as antes de sair ou descarte-as?" unsavedTitle = "Alterações não salvas" -uploadUnsupported = "Arquivos enviados ainda não são suportados em pipelines, então estas etapas não podem ser salvas: {{tools}}." usesDefaults = "Executa com configurações padrão" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combina todos os arquivos da etapa anterior" @@ -7825,31 +7778,36 @@ undeclared-operation = "Não é possível verificar o que esta etapa aceita" [portal.pipelines.composer] addTool = "Adicionar ferramenta" -cancel = "Cancelar" -chainEmpty = "Adicione uma ferramenta para começar a criar seu pipeline." create = "Criar pipeline" +createPaused = "Create paused" editingUnsupported = "A exibição destes parâmetros da ferramenta para edição ainda não é suportada." -moveDown = "Mover para baixo" -moveUp = "Mover para cima" +editSource = "Edit source" name = "Nome" namePlaceholder = "ex.: Varredura de redação" noToolSettings = "Esta ferramenta não tem configurações ajustáveis." -operations_one = "Operação ({{count}})" -operations_other = "Operações ({{count}})" output = "Saída" -removeStep = "Remover operação" save = "Salvar alterações" scheduleEvery = "Executar a cada" -sources = "Fontes" -sourcesLoading = "Carregando fontes..." trigger = "Gatilho" triggerManual = "Somente manual" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dias" hours = "horas" minutes = "minutos" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Excluir \"{{name}}\"? Isso não pode ser desfeito." cancel = "Cancelar" @@ -7867,6 +7825,37 @@ connectSource = "Conectar uma fonte" description = "Crie seu primeiro pipeline: escolha as fontes em que ele é executado, encadeie as operações e escolha para onde vai a saída." title = "Ainda não há pipelines" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Ativos" paused = "Pausados" @@ -7879,7 +7868,6 @@ completed_one = "Execução concluída." completed_other = "Todas as {{count}} execuções concluídas." empty = "Nada para executar: as fontes não tinham documentos para processar." failed = "Falha na execução: {{error}}" -historyCleared = "Histórico limpo. A próxima execução reprocessa tudo que está atualmente nas fontes." inFlight = "Nada novo para executar: documentos ainda estão sendo processados de uma execução anterior." parked_one = "Nada para executar: {{count}} documento falhou anteriormente e está estacionado. Corrija a causa e limpe o histórico para tentar novamente." parked_other = "Nada para executar: {{count}} documentos falharam anteriormente e estão estacionados. Corrija a causa e limpe o histórico para tentar novamente." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Fontes" status = "Status" steps = "Etapas" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Monitoramento de pasta" @@ -8209,6 +8198,24 @@ label = "Anexar a uma issue do Jira" description = "Envia o documento processado como anexo." label = "Enviar o documento por email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Grava o documento processado em uma pasta." label = "Upload para Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Esgotado" subtitle = "Seu teste gratuito vai até {{date}}. Não é necessário cartão." title = "Teste Enterprise" -[portal.search] -ariaLabel = "Pesquisar" -placeholder = "Pesquisar no Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Ações rápidas aparecerão aqui assim que estiverem disponíveis." -noActionsTitle = "Nenhuma ação rápida" -noMatches = "Nenhuma correspondência para \"{{query}}\"" -noMatchesDescription = "Tente uma palavra-chave diferente ou navegue pelo catálogo." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Cancelar" confirm = "Excluir" title = "Excluir fonte?" -[portal.sources.empty] -description = "Conecte uma pasta (e, em breve, armazenamento em nuvem) para que suas políticas tenham de onde puxar documentos." -title = "Nenhuma fonte conectada ainda" - [portal.sources.kpi] inUse = "Em uso" total = "Conexões" @@ -8785,6 +8778,7 @@ unused = "Não usada" documents = "Documentos" source = "Fonte" status = "Status" +type = "Type" usedBy = "Políticas" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Policies & Privacy" [settings.preferences] title = "Preferences" -[settings.search] -placeholder = "Search settings pages..." - [settings.security] description = "Update your password to keep your account secure." title = "Security" @@ -10128,6 +10119,7 @@ last = "Last page" maintainRatio = "Toggle maintain aspect ratio" next = "Next page" noSavedSigs = "No saved signatures found" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Personal Signatures" previous = "Previous page" redo = "Redo" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Background removal failed" hint = "Upload a PNG or JPG image of your signature" label = "Upload signature image" placeholder = "Select image file" +previewAlt = "Current image signature" processing = "Processing image..." removeBackground = "Remove white background (make transparent)" @@ -10179,6 +10172,17 @@ saved = "Select a saved signature above, then click anywhere on the PDF to place text = "After entering your name above, click anywhere on the PDF to place your signature." title = "How to add signature" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Move Signature" pause = "Pause placement" @@ -10625,6 +10629,23 @@ title = "Upload to Server" updateButton = "Update on Server" uploadButton = "Upload to Server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Go back" pdfTools = "Ferramentas PDF" placeholder = "Choose a tool to get started" premiumFeature = "Premium feature:" -searchTools = "Pesquisar ferramentas" toolsHeader = "Tools" viewAllTools = "View all tools" @@ -10822,6 +10842,9 @@ removal = "Removal" signing = "Signing" verification = "Verification" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "No tools found" noTools = "No tools available" @@ -10979,6 +11002,13 @@ approver = "Aprova política" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Cancelar o convite para {{email}}? A pessoa não poderá entrar com o link atual." cancelInviteTitle = "Cancelar convite" @@ -10998,10 +11028,8 @@ title = "Nenhum membro ainda" addToTeam = "Adicionar à equipe" guestCount = "{{count}} convidado" guests = "Convidados" -guestsDesc = "Colaboradores externos, limitados ao que você compartilhou. Somente Editor." ledBy = "liderada por {{owner}}" org = "Organização" -orgDesc = "Proprietários com autoridade em toda a organização e aprovação de políticas" owners = "{{count}} proprietário" team = "Equipe {{name}}" teamMeta = "{{count}} pessoas" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Convidado por {{who}}" cancel = "Cancelar" -count = "{{count}} pendentes" -desc = "Pessoas convidadas que ainda não entraram. Elas ocupam uma vaga até aceitarem." expiresInDays_one = "Expira em {{count}} dia" expiresInDays_other = "Expira em {{count}} dias" expiresToday = "Expira hoje" title = "Convites pendentes" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Algo deu errado ao acessar o backend, ou você não tem acesso. Tente novamente." title = "Não foi possível carregar os membros" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Search attachments" title = "Attachments" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Título do marcador" bookmarkTitleRequired = "O título do marcador é obrigatório" closeSidebar = "Fechar barra lateral de marcadores" collapseAll = "Recolher todos os marcadores" +empty = "No bookmarks in this document" expandAll = "Expandir todos os marcadores" searchPlaceholder = "Pesquisar marcadores" +title = "Bookmarks" [viewer.comments] addComment = "Add comment" @@ -11264,10 +11297,12 @@ locateAnnotation = "Locate in document" moreActions = "More actions" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Page {{page}}" placingHint = "Clique em uma página para posicionar… (cancelar)" removeCommentOnly = "Remove comment only" saveReply = "Save reply" +searchPlaceholder = "Search comments" title = "Comments" typeComment = "Comment" typeInsertText = "Insert Text" @@ -11290,6 +11325,7 @@ unsavedDesc = "You have unsaved changes" closeSidebar = "Fechar barra lateral de camadas" hideAll = "Ocultar todas as camadas" showAll = "Mostrar todas as camadas" +title = "Layers" [viewer.link] delete = "Delete link" @@ -11646,6 +11682,7 @@ exitRedaction = "Exit Redaction Mode" exportAll = "Export PDF" exportSelected = "Export Selected Pages" formFill = "Fill Form" +hideToolbar = "Hide toolbar" multiTool = "Multi-Tool" panMode = "Pan Mode" print = "Print PDF" @@ -11666,6 +11703,7 @@ selectAll = "Select All" selectByNumber = "Select by Page Numbers" selectLanguage = "Select language" share = "Compartilhar" +showToolbar = "Show toolbar" toggleAnnotations = "Toggle Annotations Visibility" toggleAttachments = "Toggle Attachments" toggleBookmarks = "Toggle Bookmarks" diff --git a/frontend/editor/public/locales/pt-PT/translation.toml b/frontend/editor/public/locales/pt-PT/translation.toml index de2b6efa79..159dd5439d 100644 --- a/frontend/editor/public/locales/pt-PT/translation.toml +++ b/frontend/editor/public/locales/pt-PT/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabeto" apply = "Aplicar" applyAndContinue = "Guardar e sair" -areYouSure = "Tem a certeza de que pretende sair?" back = "Voltar" black = "Preto" blue = "Azul" @@ -106,6 +105,7 @@ undoSuccess = "Operação desfeita com sucesso" unknown = "Desconhecido" unpin = "Desafixar ficheiro (substituir após executar a ferramenta)" unsavedChanges = "Tem alterações não guardadas no seu PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Alterações não guardadas" unsupported = "Não suportado" username = "Nome de utilizador" @@ -1879,6 +1879,9 @@ width = "Largura" [app] description = "A alternativa gratuita ao Adobe Acrobat (10M+ transferências)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Converter para PDF/A-3b" convertToPdfA3bDescription = "Cria um PDF de arquivo com anexos incorporados" @@ -2933,12 +2936,16 @@ text = "Para tornar estas permissões imutáveis, use a ferramenta Adicionar pal [chat.actions] copy = "Copiar mensagem" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opções do agente Stirling" +assistant = "AI Assistant" clearChat = "Limpar chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Esta operação irá usar os seus créditos cloud" tooltip = "Escolher cor do ecrã" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Escolher cor" [common] +actions = "Actions" back = "Voltar" cancel = "Cancelar" close = "Fechar" +codeSample = "Code sample" collapse = "Recolher" confirm = "Confirmar" continue = "Continuar" copied = "Copiado!" copy = "Copiar" done = "Concluído" +enterValue = "Enter value" error = "Erro" expand = "Expandir" loading = "A carregar..." next = "Seguinte" +open = "Open" preview = "Pré-visualizar" previous = "Anterior" refresh = "Atualizar" remaining = "Restantes" retry = "Tentar novamente" save = "Guardar" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Limpar selecionados" @@ -3185,6 +3199,7 @@ title = "Método de compressão" [compress.settings] desiredSize = "Tamanho de ficheiro pretendido" desiredSizePlaceholder = "Introduzir tamanho" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "A compressão é uma forma simples de reduzir o tamanho do seu ficheiro. Escolha Tamanho do ficheiro para introduzir um tamanho alvo e deixarmos ajustar a qualidade por si. Escolha Qualidade para definir manualmente a intensidade da compressão." @@ -3387,6 +3402,24 @@ pdfOptions = "Opções de PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Selecione primeiro um formato de origem" settings = "Definições" single = "Único" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Pesquisar..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Editar" @@ -3824,11 +3861,13 @@ shareSelected = "Partilhar selecionados" sharing = "Partilha" showAll = "Mostrar tudo" showHistory = "Mostrar histórico" +sortBy = "Sort files" sortByDate = "Ordenar por data" sortByName = "Ordenar por nome" sortBySize = "Ordenar por tamanho" storage = "Armazenamento" storageState = "Armazenamento" +storageUsed = "Storage used" synced = "Sincronizado" title = "Carregar ficheiros PDF" toolChain = "Ferramentas aplicadas" @@ -3874,8 +3913,12 @@ addFiles = "Adicionar ficheiros" addingFiles = "A adicionar ficheiros…" collapse = "Recolher barra lateral" customizeGroups = "Personalizar grupos" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Abra ficheiros para começar" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Expandir barra lateral" googleDrive = "Google Drive" googleDriveDisabled = "O Google Drive não está configurado" @@ -3888,15 +3931,17 @@ openFromComputer = "Abrir do computador" openSettings = "Abrir definições" other = "Outro" recent = "Recente" -search = "Pesquisar" -searchPlaceholder = "Pesquisar ficheiros..." viewAll = "Ver todos os {{count}} ficheiros" [fileSidebar.fileItem] closeViewer = "Fechar visualizador" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Abrir no visualizador" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Repor predefinições" subtitle = "Agrupe os seus ficheiros em categorias principais. Adicione etiquetas existentes ou novas a uma categoria, mude o nome ou crie a sua própria. Ficheiros que não estejam em nenhuma das suas categorias aparecem em “Outro”." title = "Categorias da barra lateral" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Adicionar ao espaço de trabalho" addToWorkspaceCount = "Adicionar {{count}} ao espaço de trabalho" @@ -3918,7 +3971,6 @@ backToMyFiles = "Voltar a Os meus ficheiros" breadcrumbs = "Caminho da pasta" cancel = "Cancelar" classification = "Classificação" -clearSearch = "Limpar pesquisa" clearSelection = "Limpar seleção" closeDetails = "Fechar detalhes" create = "Criar" @@ -3950,6 +4002,7 @@ downloadAll = "Transferir todos" downloadVersion = "Transferir esta versão" dropOverlay = "Largar ficheiros para carregar" dropOverlaySub = "Os ficheiros começam em Local. Use 'Mover para' ou 'Guardar na nuvem' para os organizar numa pasta." +duplicate = "Duplicate" file = "Ficheiro" fileInfo = "File info" fileMenu = "Ações do ficheiro" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Redimensionar árvore de pastas (teclas de seta, Shift para save = "Guardar" saveToServer = "Guardar no servidor" saveToServerDisabledHint = "Guardar no servidor não está ativado neste servidor. Peça ao seu administrador para o ativar." -search = "Pesquisar" -searchPlaceholder = "Pesquisar nesta pasta e subpastas" selectAll = "Selecionar tudo" selectAllHint = "Clique para selecionar tudo. Dica: mantenha Ctrl (ou Cmd) para adicionar ficheiros um a um, Shift para selecionar um intervalo." selectedCount = "{{count}} selecionados" @@ -4044,6 +4095,10 @@ title = "Ainda não há ficheiros na nuvem" hint = "Os ficheiros guardados sem carregar ficam aqui. Largue um ficheiro para adicionar." title = "Sem ficheiros apenas locais" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Os ficheiros que abrir ou editar aparecerão aqui." title = "Ainda nada modificado" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Não foi possível eliminar a pasta." deleteFolderFailedDetail = "Não foi possível eliminar a pasta: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Não foi possível atualizar a aparência da pasta." folderAppearanceFailedDetail = "Não foi possível atualizar a aparência da pasta: {{message}}" moveFilesFailed = "Não foi possível mover os ficheiros." @@ -4113,6 +4170,11 @@ localHint = "Apenas armazenado neste navegador" shared = "Partilhado" sharedHint = "Partilhado consigo via ligação" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Mais antigos primeiro" modifiedDesc = "Mais recentes primeiro" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Sem ficheiros disponíveis no armazenamento. Abra alguns open = "Abrir" openFile = "Abrir ficheiro" openFiles = "Abrir ficheiros" +selectFile = "Select {{name}}" selectFromStorage = "Selecionar do armazenamento" upload = "Carregar" uploadFile = "Carregar ficheiro" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Tudo guardado" +analyzingFields = "Analysing form fields..." extractCsvError = "Falha ao extrair CSV" extractXlsxError = "Falha ao extrair XLSX" +filled = "filled" flattenAfterFilling = "Achatar após o preenchimento" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obrig." requiredFieldsError = "Preencha todos os campos obrigatórios" rescanFields = "Reanalisar campos" @@ -4795,9 +4863,6 @@ title = "Imagem para PDF" [imageToPdf] tags = "conversão,img,jpg,imagem,foto" -[infoBanner] -dismiss = "Ignorar" - [invite] acceptError = "Falha ao criar conta" accountFor = "A criar conta para" @@ -4979,6 +5044,34 @@ uploadSuccess = "Carregamento concluído!" uploadSuccessMessage = "As suas imagens foram enviadas." validating = "A validar a sessão..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Digitalize para carregar fotos. As imagens convertem-se automaticamente em PDF." descriptionNoConvert = "Digitalize para carregar fotos a partir do seu dispositivo móvel." @@ -4997,8 +5090,13 @@ title = "Carregar do telemóvel" tags = "Multi Ferramenta,Multi operação,UI,clicar arrastar,front end,lado cliente,interativo,interagível,mover,eliminar,migrar,dividir" title = "Multi Ferramenta PDF" -[navbar] -search = "Pesquisar" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "A autenticação não foi bem-sucedida. Pode fechar esta janela e tentar novamente." @@ -5127,6 +5225,7 @@ activeFiles = "A vista Ficheiros ativos mostra todos os PDFs qu allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Fechar" cropSettings = "Agora que selecionámos o ficheiro que queremos recortar, podemos configurar a ferramenta Cortar para escolher a área para a qual queremos recortar o PDF." +dialogLabel = "Onboarding" fileCheckbox = "Clicar num dos ficheiros seleciona-o para processamento. Pode selecionar múltiplos ficheiros para operações em lote." fileReplacement = "O ficheiro modificado substituirá automaticamente o ficheiro original na Área de trabalho, permitindo-lhe executá-lo facilmente em mais ferramentas." filesButton = "O botão Ficheiros na barra de Acesso Rápido permite-lhe carregar PDFs para usar as ferramentas." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF Para PDF/A" tags = "arquivo,longo prazo,padrão,conversão,armazenamento,preservação" title = "PDF Para PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "impressão,padrão,conversão,produção,pré-impressão,arquivo" title = "PDF para PDF/X" @@ -6045,6 +6151,7 @@ small = "500 créditos" xsmall = "100 créditos" [plan.availablePlans] +currency = "Billing currency" subtitle = "Escolha o plano que se adapta às suas necessidades" title = "Planos disponíveis" @@ -6624,6 +6731,7 @@ reachedTitle = "Limite mensal de gastos atingido" title = "Não foi possível abrir o portal Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "de {{allowance}} PDF gratuitos utilizados" capSuffix_other = "de {{allowance}} PDF gratuitos utilizados" eyebrow = "Avaliação do Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Envie um documento ou uma notificação por email quando uma política for executada." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Arquive documentos processados no Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDKs oficiais" beta = "Beta" deprecated = "Obsoleto" -[portal.docs.search] -empty = "Nenhuma documentação correspondente" -placeholder = "Pesquisar documentação" -results = "{{count}} resultados" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Capacidades incorporadas e nomeadas que o seu agente invoca como uma única ferramenta. Cada skill é uma cadeia de operações determinística com avaliações associadas." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Nenhum documento corresponde a este filtro." rowActions = "Ações da linha" sensitiveLabel = "Sensível" -sensitiveTitle = "Sensível — acesso necessário" [portal.documents.table.columns] action = "Pipeline / Ação" document = "Documento" +labels = "Labels" product = "Produto" status = "Estado" time = "Hora" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Visto pela última vez" region = "Região" status = "Estado" +target = "Target" version = "Versão" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Esta vista encontrou um erro inesperado. Tente novamente ou escol retry = "Tentar novamente" title = "Algo correu mal nesta página" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Voltar" body = "Transfira a aplicação de desktop ou autoaloje o servidor." @@ -7380,11 +7560,6 @@ cancel = "Cancelar" confirm = "Revogar chave" title = "Revogar chave API" -[portal.infrastructure.attestationLabel] -attested = "Atestado" -inScope = "No âmbito" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtrar eventos de auditoria por categoria" heading = "Registos de auditoria" @@ -7456,11 +7631,6 @@ info = "Info" success = "Sucesso" warning = "Aviso" -[portal.infrastructure.certLabel] -certified = "Certificado" -inProgress = "Em curso" -notStarted = "Não iniciado" - [portal.infrastructure.createKey] cancel = "Cancelar" createKey = "Criar chave" @@ -7474,238 +7644,10 @@ subtitleCreated = "Copie este segredo agora — não será mostrado novamente." title = "Criar chave API" titleCreated = "Chave criada" -[portal.infrastructure.deployLabel] -live = "Em direto" -queued = "Em fila" -rolledBack = "Revertido" -rolling = "A implementar" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Implementado por" -environment = "Ambiente" -product = "Produto" -status = "Estado" -version = "Versão" -when = "Quando" - -[portal.infrastructure.deployments.recent] -heading = "Implementações recentes" -subheading = "Os rollouts mais recentes em produtos e ambientes." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instâncias" -latency = "Latência" -load = "Carga" -p99 = "P99" -region = "Região" -status = "Estado" -throughput = "Débito" -uptime = "Tempo de atividade" -version = "Versão" - -[portal.infrastructure.deployments.regions] -heading = "Regiões" -subheading = "Estado em tempo real de cada região Stirling implementada — latência, carga e versão do rollout." - -[portal.infrastructure.deployments.regions.empty] -description = "As regiões implementadas aparecem aqui assim que o seu espaço de trabalho for aprovisionado." -title = "Nenhuma região implementada" - [portal.infrastructure.keyLabel] active = "Ativa" revoked = "Revogada" -[portal.infrastructure.modelLabel] -active = "Ativo" -degraded = "Degradado" -disabled = "Desativado" - -[portal.infrastructure.models] -heading = "Modelos" -msValue = "{{value}} ms" -subheading = "O catálogo de modelos e encaminhamento que alimenta o processamento de documentos em todo o seu espaço de trabalho." - -[portal.infrastructure.models.byom] -description = "Registe um modelo on-prem ou autoalojado e fixe-o a uma região para processamento vinculado à residência dos dados." -title = "Traga o seu próprio modelo" - -[portal.infrastructure.models.catalogue] -heading = "Catálogo" -sub = "Modelos geridos disponíveis para o seu espaço de trabalho, com latência e custo em tempo real." -subEnterprise = "Modelos geridos, próprios e on-prem — com fixação por região disponível." - -[portal.infrastructure.models.catalogue.empty] -description = "Os modelos no catálogo do seu espaço de trabalho aparecem aqui." -title = "Não há modelos disponíveis" - -[portal.infrastructure.models.columns] -cost = "Custo" -latency = "Latência" -load = "Carga" -model = "Modelo" -status = "Estado" -type = "Tipo" -version = "Versão" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/chamada" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Modelos ativos" -avgLatency = "Latência média" -included = "Incluído" -monthlySpend = "Gasto mensal com modelos" - -[portal.infrastructure.models.routing] -empty = "Não há regras de encaminhamento configuradas." -heading = "Regras de encaminhamento" -sub = "Qual o modelo que trata cada operação. A predefinição aplica-se quando nenhuma regra mais específica corresponde." -subLocked = "Encaminhe operações para modelos específicos — disponível em planos pagos." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Atualize para Pro para controlar qual o modelo que trata cada operação e tipo de documento." -title = "O encaminhamento de modelos é uma funcionalidade paga" - -[portal.infrastructure.models.routingColumns] -default = "Predefinição" -docType = "Tipo de documento" -modelForAria = "Modelo para {{operation}}" -operation = "Operação" -routedTo = "Encaminhado para" - -[portal.infrastructure.modelTypeLabel] -classification = "Classificação" -extraction = "Extração" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degradado" -down = "Indisponível" -healthy = "Saudável" - -[portal.infrastructure.security.access.byok] -description = "Forneça uma chave do seu próprio KMS. O Stirling cifra com ela, mas ainda consegue ler." -label = "Traga a sua própria chave (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "As chaves nunca saem do seu KMS. O Stirling mantém apenas texto cifrado." -label = "Mantenha a sua própria chave (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "O Stirling gere as chaves de cifra. O mais simples — zero operações de chaves do seu lado." -label = "Chaves detidas pelo Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Política de acesso a documentos" -subheading = "Controla quem pode decifrar documentos processados em repouso." - -[portal.infrastructure.security.attestations] -heading = "Atestados de conformidade" -noReport = "Nenhum relatório disponível" -subheading = "Postura de auditoria por framework, com relatórios disponíveis sobre controlos atestados." -viewReport = "Ver relatório →" - -[portal.infrastructure.security.compliance] -heading = "Conformidade" -subheading = "Atestados e certificações que abrangem a plataforma Stirling." - -[portal.infrastructure.security.empty] -description = "A configuração de segurança do seu espaço de trabalho aparecerá aqui." -title = "Postura de segurança indisponível" - -[portal.infrastructure.security.hyokBanner] -description = "Com HYOK, as chaves de cifra nunca saem do seu KMS. O Stirling armazena e processa apenas texto cifrado que pode revogar a qualquer momento." -title = "O Stirling não consegue decifrar os seus documentos" - -[portal.infrastructure.security.ipAllowlist] -empty = "Não há intervalos de IP configurados — todos os IPs são permitidos." -heading = "Lista de permissões de IP" -sub = "O acesso à API está restrito a estes intervalos CIDR." -subLocked = "Restrinja o acesso à API a intervalos de IP conhecidos — disponível em planos pagos." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Atualize para Pro para restringir o acesso à API a redes específicas." -title = "A lista de permissões de IP é uma funcionalidade paga" - -[portal.infrastructure.security.ipColumns] -added = "Adicionado" -addedBy = "Adicionado por" -cidr = "CIDR" -label = "Etiqueta" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritmo" -heading = "Gestão de chaves de cifra" -keyId = "Identificador da chave" -lastRotated = "Rodada pela última vez" -rotateKey = "Rodar chave" -rotationPolicy = "Política de rotação" -subheading = "Custódia das chaves que cifram documentos em repouso — quem pode decifrar e como as chaves rodam." - -[portal.infrastructure.security.managedBanner] -description = "A custódia traga-a-sua-própria-chave (BYOK) e mantenha-a-sua-própria-chave (HYOK) está disponível no Enterprise. Atualize para fornecer chaves do seu próprio KMS." -title = "As chaves são geridas pelo Stirling no seu plano" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Ásia-Pacífico" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · limite de dados GDPR" -label = "União Europeia" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Estados Unidos" - -[portal.infrastructure.security.residencyHeader] -heading = "Residência dos dados" -subheading = "Onde os documentos são armazenados e processados." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} usado" - -[portal.infrastructure.storage.empty] -description = "O armazenamento ligado e a utilização aparecem aqui." -title = "Nenhum armazenamento configurado" - -[portal.infrastructure.storage.lifecycle] -active = "Ativo" -activeRange = "0–{{value}}d" -archived = "Arquivado" -coldStorage = "armazenamento frio" -deleted = "Eliminado" -never = "nunca" -purged = "purgado" - -[portal.infrastructure.storage.providers] -connect = "Ligar" -connected = "Ligado" -heading = "Fornecedores ligados" -subheading = "Onde os artefactos processados são escritos." - -[portal.infrastructure.storage.retention] -heading = "Retenção" -subheading = "Durante quanto tempo os artefactos são mantidos antes da eliminação do ciclo de vida." -windowLabel = "Janela de retenção predefinida" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dia" -days_other = "{{count}} dias" -never = "Nunca eliminar" - -[portal.infrastructure.storage.totalUsage] -heading = "Utilização total" -progressLabel = "Armazenamento usado" -subheading = "Armazenamento consumido em todos os fornecedores ligados." - [portal.infrastructure.tabs] apiKeys = "Chaves API" audit = "Registos de auditoria" @@ -7715,11 +7657,9 @@ security = "Segurança" storage = "Armazenamento" [portal.integrations] -addAnother = "Adicionar outra" availableHeading = "Disponíveis" comingSoonHeading = "Brevemente" connect = "Ligar" -connectedHeading = "Ligadas" connectionCount_one = "{{count}} ligação" connectionCount_other = "{{count}} ligações" customApi = "API personalizada" @@ -7737,6 +7677,10 @@ security = "Segurança" signing = "Assinatura" storage = "Armazenamento" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Ligada" @@ -7767,7 +7711,6 @@ integrations = "Integrações" pipelines = "Pipelines" policies = "Políticas" procurement = "Aquisições" -settings = "Definições" sources = "Fontes" usage = "Utilização e faturação" users = "Utilizadores" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Nova pipeline" [portal.pipelines.builder] -addStep = "Adicionar ferramenta" +activate = "Activate" back = "Voltar às pipelines" cannotFollow = "Não é possível receber {{produced}}" chooseAccount = "Escolha uma conta" @@ -7792,28 +7735,38 @@ chooseDestination = "Escolha um destino" chooseOperation = "Escolha o que este passo faz" chooseSource = "Escolha uma fonte" discard = "Descartar alterações" -enabled = "Ativada" inputs = "Entrada" inputSource = "Fonte de entrada" inputTrigger = "Acionador" keepEditing = "Continuar a editar" +moreActions = "More actions" needsConfiguring = "Precisa de configuração" -needsUpload = "Precisa de um ficheiro carregado" -noSources = "Ainda não há fontes ligadas. Crie uma para a usar como entrada." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Nenhuma ferramenta corresponde à sua pesquisa." -pipelineSettings = "Definições da pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Pesquisar ferramentas" -selectToolBody = "Adicione uma ferramenta para criar a sua pipeline." -selectToolTitle = "Ainda não há ferramentas" sendToSystem = "Enviar para outro sistema" stepsIncompatible = "Estes passos não podem ser executados sobre o que o passo anterior produz: {{tools}}." stepsNeedSetup = "Estes passos ainda precisam de configuração antes de guardar: {{tools}}." -toolSettings = "Definições da ferramenta" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Operação não reconhecida, mantida tal como está." unsavedBody = "Tem alterações não guardadas. Guarde-as antes de sair ou descarte-as?" unsavedTitle = "Alterações não guardadas" -uploadUnsupported = "Os ficheiros carregados ainda não são suportados em pipelines, por isso estes passos não podem ser guardados: {{tools}}." usesDefaults = "Executa com definições predefinidas" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combina todos os ficheiros do passo anterior" @@ -7825,31 +7778,36 @@ undeclared-operation = "Não é possível verificar o que este passo aceita" [portal.pipelines.composer] addTool = "Adicionar ferramenta" -cancel = "Cancelar" -chainEmpty = "Adicione uma ferramenta para começar a criar a sua pipeline." create = "Criar pipeline" +createPaused = "Create paused" editingUnsupported = "A apresentação destes parâmetros da ferramenta para edição ainda não é suportada." -moveDown = "Mover para baixo" -moveUp = "Mover para cima" +editSource = "Edit source" name = "Nome" namePlaceholder = "ex.: Varredura de redação" noToolSettings = "Esta ferramenta não tem definições configuráveis." -operations_one = "Operação ({{count}})" -operations_other = "Operações ({{count}})" output = "Saída" -removeStep = "Remover operação" save = "Guardar alterações" scheduleEvery = "Executar a cada" -sources = "Fontes" -sourcesLoading = "A carregar fontes..." trigger = "Acionador" triggerManual = "Apenas manual" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dias" hours = "horas" minutes = "minutos" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Eliminar \"{{name}}\"? Esta ação não pode ser anulada." cancel = "Cancelar" @@ -7867,6 +7825,37 @@ connectSource = "Ligar uma fonte" description = "Crie a sua primeira pipeline: escolha as fontes sobre as quais é executada, encadeie as operações e escolha para onde vai a saída." title = "Ainda não há pipelines" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Ativas" paused = "Em pausa" @@ -7879,7 +7868,6 @@ completed_one = "Execução concluída." completed_other = "Todas as {{count}} execuções foram concluídas." empty = "Nada para executar: as fontes não tinham documentos para processar." failed = "Falha na execução: {{error}}" -historyCleared = "Histórico limpo. A próxima execução reprocessa tudo o que está atualmente nas fontes." inFlight = "Nada de novo a executar: os documentos ainda estão a ser processados a partir de uma execução anterior." parked_one = "Nada a executar: {{count}} documento falhou anteriormente e está estacionado. Corrija a causa e depois limpe o histórico para o tentar novamente." parked_other = "Nada a executar: {{count}} documentos falharam anteriormente e estão estacionados. Corrija a causa e depois limpe o histórico para os tentar novamente." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Fontes" status = "Estado" steps = "Passos" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Monitorização de pasta" @@ -8209,6 +8198,24 @@ label = "Anexar a uma incidência Jira" description = "Envia o documento processado como anexo." label = "Enviar o documento por email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Grava o documento processado numa pasta." label = "Carregar para Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Esgotado" subtitle = "O seu teste gratuito decorre até {{date}}. Não é necessário cartão." title = "Teste Enterprise" -[portal.search] -ariaLabel = "Pesquisar" -placeholder = "Pesquisar no Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "As ações rápidas aparecerão aqui assim que estiverem disponíveis." -noActionsTitle = "Sem ações rápidas" -noMatches = "Sem resultados para \"{{query}}\"" -noMatchesDescription = "Experimente uma palavra-chave diferente ou navegue pelo catálogo." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Cancelar" confirm = "Eliminar" title = "Eliminar origem?" -[portal.sources.empty] -description = "Ligue uma pasta (e, em breve, armazenamento na cloud) para que as suas políticas tenham de onde obter documentos." -title = "Ainda não há origens ligadas" - [portal.sources.kpi] inUse = "Em utilização" total = "Ligações" @@ -8785,6 +8778,7 @@ unused = "Não utilizada" documents = "Documentos" source = "Origem" status = "Estado" +type = "Type" usedBy = "Políticas" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Políticas e Privacidade" [settings.preferences] title = "Preferências" -[settings.search] -placeholder = "Procurar páginas de definições..." - [settings.security] description = "Atualize a sua palavra-passe para manter a sua conta segura." title = "Segurança" @@ -10128,6 +10119,7 @@ last = "Última página" maintainRatio = "Alternar manter proporção" next = "Próxima página" noSavedSigs = "Nenhuma assinatura guardada encontrada" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Assinaturas Pessoais" previous = "Página anterior" redo = "Refazer" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Falha na remoção do fundo" hint = "Carregue uma imagem PNG ou JPG da sua assinatura" label = "Carregar imagem da assinatura" placeholder = "Selecione ficheiro de imagem" +previewAlt = "Current image signature" processing = "A processar imagem..." removeBackground = "Remover fundo branco (tornar transparente)" @@ -10179,6 +10172,17 @@ saved = "Selecione uma assinatura guardada acima e depois clique em qualquer par text = "Depois de introduzir o seu nome acima, clique em qualquer lugar no PDF para colocar a sua assinatura." title = "Como adicionar a assinatura" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Mover Assinatura" pause = "Pausar colocação" @@ -10625,6 +10629,23 @@ title = "Carregar para o servidor" updateButton = "Atualizar no servidor" uploadButton = "Carregar para o servidor" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Voltar" pdfTools = "Ferramentas PDF" placeholder = "Escolha uma ferramenta para começar" premiumFeature = "Funcionalidade premium:" -searchTools = "Pesquisar ferramentas" toolsHeader = "Ferramentas" viewAllTools = "Ver todas as ferramentas" @@ -10822,6 +10842,9 @@ removal = "Remoção" signing = "Assinatura" verification = "Verificação" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nenhuma ferramenta encontrada" noTools = "Nenhuma ferramenta disponível" @@ -10979,6 +11002,13 @@ approver = "Aprova política" editor = "Editor" processor = "Processador" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Cancelar o convite para {{email}}? A pessoa não poderá aderir com a ligação atual." cancelInviteTitle = "Cancelar convite" @@ -10998,10 +11028,8 @@ title = "Ainda sem membros" addToTeam = "Adicionar à equipa" guestCount = "{{count}} convidado" guests = "Convidados" -guestsDesc = "Colaboradores externos, limitados ao que partilhou. Apenas Editor." ledBy = "liderada por {{owner}}" org = "Organização" -orgDesc = "Proprietários com autoridade em toda a organização e aprovação de políticas" owners = "{{count}} proprietário" team = "Equipa {{name}}" teamMeta = "{{count}} pessoas" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Convidado por {{who}}" cancel = "Cancelar" -count = "{{count}} pendentes" -desc = "Pessoas convidadas que ainda não aderiram. Ocupam um lugar até aceitarem." expiresInDays_one = "Expira em {{count}} dia" expiresInDays_other = "Expira em {{count}} dias" expiresToday = "Expira hoje" title = "Convites pendentes" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Ocorreu um erro ao contactar o backend, ou não tem acesso. Tente novamente." title = "Não foi possível carregar os membros" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Pesquisar anexos" title = "Anexos" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Título do marcador" bookmarkTitleRequired = "O título do marcador é obrigatório" closeSidebar = "Fechar barra lateral de marcadores" collapseAll = "Recolher todos os marcadores" +empty = "No bookmarks in this document" expandAll = "Expandir todos os marcadores" searchPlaceholder = "Pesquisar marcadores" +title = "Bookmarks" [viewer.comments] addComment = "Adicionar comentário" @@ -11264,10 +11297,12 @@ locateAnnotation = "Localizar no documento" moreActions = "Mais ações" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Página {{page}}" placingHint = "Clique numa página para colocar… (cancelar)" removeCommentOnly = "Remover apenas o comentário" saveReply = "Guardar resposta" +searchPlaceholder = "Search comments" title = "Comentários" typeComment = "Comentário" typeInsertText = "Inserir texto" @@ -11290,6 +11325,7 @@ unsavedDesc = "Tem alterações por guardar" closeSidebar = "Fechar barra lateral de camadas" hideAll = "Ocultar todas as camadas" showAll = "Mostrar todas as camadas" +title = "Layers" [viewer.link] delete = "Eliminar ligação" @@ -11646,6 +11682,7 @@ exitRedaction = "Sair do modo de expurgo" exportAll = "Exportar PDF" exportSelected = "Exportar páginas selecionadas" formFill = "Preencher formulário" +hideToolbar = "Hide toolbar" multiTool = "Multi-ferramenta" panMode = "Modo de deslocação" print = "Imprimir PDF" @@ -11666,6 +11703,7 @@ selectAll = "Selecionar tudo" selectByNumber = "Selecionar por números de página" selectLanguage = "Selecionar idioma" share = "Partilhar" +showToolbar = "Show toolbar" toggleAnnotations = "Alternar visibilidade das anotações" toggleAttachments = "Alternar anexos" toggleBookmarks = "Alternar marcadores" diff --git a/frontend/editor/public/locales/ro-RO/translation.toml b/frontend/editor/public/locales/ro-RO/translation.toml index 56477b43d5..721c051539 100644 --- a/frontend/editor/public/locales/ro-RO/translation.toml +++ b/frontend/editor/public/locales/ro-RO/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabet" apply = "Aplicare" applyAndContinue = "Salvați și ieșiți" -areYouSure = "Sigur doriți să părăsiți?" back = "Înapoi" black = "Negru" blue = "Albastru" @@ -106,6 +105,7 @@ undoSuccess = "Operațiune anulată cu succes" unknown = "Necunoscut" unpin = "Anulare fixare fișier (înlocuiește după rularea instrumentului)" unsavedChanges = "Aveți modificări nesalvate în PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Modificări nesalvate" unsupported = "Nesuportat" username = "Nume de utilizator" @@ -1879,6 +1879,9 @@ width = "Lățime" [app] description = "Alternativa gratuită la Adobe Acrobat (10M+ descărcări)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Convertește la PDF/A-3b" convertToPdfA3bDescription = "Creează un PDF pentru arhivare cu atașamente încorporate" @@ -2933,12 +2936,16 @@ text = "Pentru ca aceste permisiuni să nu poată fi schimbate, utilizați instr [chat.actions] copy = "Copiați mesajul" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opțiuni agent Stirling" +assistant = "AI Assistant" clearChat = "Goliți chatul" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Această operație va folosi creditele din cloud" tooltip = "Alegeți culoarea de pe ecran" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Alege culoarea" [common] +actions = "Actions" back = "Înapoi" cancel = "Anulați" close = "Închide" +codeSample = "Code sample" collapse = "Restrânge" confirm = "Confirmă" continue = "Continuă" copied = "Copiat!" copy = "Copiați" done = "Gata" +enterValue = "Enter value" error = "Eroare" expand = "Extinde" loading = "Se încarcă..." next = "Înainte" +open = "Open" preview = "Previzualizare" previous = "Înapoi" refresh = "Reîmprospătați" remaining = "Rămas" retry = "Reîncercați" save = "Salvează" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Șterge selecția" @@ -3185,6 +3199,7 @@ title = "Metodă de comprimare" [compress.settings] desiredSize = "Dimensiunea dorită a fișierului" desiredSizePlaceholder = "Introduceți dimensiunea" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Comprimarea este o modalitate ușoară de a reduce dimensiunea fișierului. Alegeți Dimensiunea fișierului pentru a introduce o dimensiune țintă și vom ajusta noi calitatea. Alegeți Calitate pentru a seta manual intensitatea comprimării." @@ -3387,6 +3402,24 @@ pdfOptions = "Opțiuni PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Selectați mai întâi un format sursă" settings = "Setări" single = "Unic" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Căutați..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Editați" @@ -3824,11 +3861,13 @@ shareSelected = "Partajați elementele selectate" sharing = "Partajare" showAll = "Afișați tot" showHistory = "Afișați istoricul" +sortBy = "Sort files" sortByDate = "Sortează după dată" sortByName = "Sortează după nume" sortBySize = "Sortează după dimensiune" storage = "Stocare" storageState = "Stocare" +storageUsed = "Storage used" synced = "Sincronizat" title = "Încărcați fișiere PDF" toolChain = "Instrumente aplicate" @@ -3874,8 +3913,12 @@ addFiles = "Adaugă fișiere" addingFiles = "Se adaugă fișiere…" collapse = "Restrânge bara laterală" customizeGroups = "Personalizează grupurile" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Deschide fișiere pentru a începe" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Extinde bara laterală" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive nu este configurat" @@ -3888,15 +3931,17 @@ openFromComputer = "Deschide de pe calculator" openSettings = "Deschide setările" other = "Altele" recent = "Recente" -search = "Căutare" -searchPlaceholder = "Caută fișiere..." viewAll = "Vezi toate cele {{count}} fișiere" [fileSidebar.fileItem] closeViewer = "Închideți vizualizatorul" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Deschideți în vizualizator" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Resetează la valorile implicite" subtitle = "Grupează fișierele în categorii părinte. Adaugă etichete existente sau noi într-o categorie, redenumește-o sau creează una proprie. Fișierele care nu se află în niciuna dintre categoriile tale apar sub „Altele”." title = "Categorii bară laterală" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Adăugați la spațiul de lucru" addToWorkspaceCount = "Adăugați {{count}} la spațiul de lucru" @@ -3918,7 +3971,6 @@ backToMyFiles = "Înapoi la Fișierele mele" breadcrumbs = "Calea folderului" cancel = "Anulați" classification = "Clasificare" -clearSearch = "Ștergeți căutarea" clearSelection = "Goliți selecția" closeDetails = "Închideți detaliile" create = "Creați" @@ -3950,6 +4002,7 @@ downloadAll = "Descărcați tot" downloadVersion = "Descărcați această versiune" dropOverlay = "Plasați fișierele pentru a încărca" dropOverlaySub = "Fișierele pornesc în Local. Folosiți „Mută în” sau „Salvează în cloud” pentru a le organiza într-un folder." +duplicate = "Duplicate" file = "Fișier" fileInfo = "File info" fileMenu = "Acțiuni fișier" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Redimensionați arborele de foldere (tastele săgeți, Shift save = "Salvați" saveToServer = "Salvați pe server" saveToServerDisabledHint = "Salvarea pe server nu este activată pe acest server. Rugați administratorul să o activeze." -search = "Căutați" -searchPlaceholder = "Căutați în acest folder și subfoldere" selectAll = "Selectați tot" selectAllHint = "Faceți clic pentru a selecta tot. Sfat: țineți apăsat Ctrl (sau Cmd) pentru a adăuga fișiere pe rând, Shift pentru a selecta un interval." selectedCount = "{{count}} selectate" @@ -4044,6 +4095,10 @@ title = "Încă nu există fișiere în cloud" hint = "Fișierele salvate fără încărcare rămân aici. Plasați un fișier pentru a adăuga unul." title = "Niciun fișier doar local" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Fișierele pe care le deschideți sau editați vor apărea aici." title = "Nimic modificat încă" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Nu s-a putut șterge folderul." deleteFolderFailedDetail = "Nu s-a putut șterge folderul: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Nu s-a putut actualiza aspectul folderului." folderAppearanceFailedDetail = "Nu s-a putut actualiza aspectul folderului: {{message}}" moveFilesFailed = "Nu s-au putut muta fișierele." @@ -4113,6 +4170,11 @@ localHint = "Stocat doar în acest browser" shared = "Partajat" sharedHint = "Partajat cu dvs. prin link" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Cele mai vechi mai întâi" modifiedDesc = "Cele mai recente mai întâi" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Nu există fișiere în stocare. Deschide mai întâi c open = "Deschide" openFile = "Deschide fișier" openFiles = "Deschide fișiere" +selectFile = "Select {{name}}" selectFromStorage = "Selectați din stocare" upload = "Încărcați" uploadFile = "Încărcați fișier" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Totul salvat" +analyzingFields = "Analysing form fields..." extractCsvError = "Extragerea CSV a eșuat" extractXlsxError = "Extragerea XLSX a eșuat" +filled = "filled" flattenAfterFilling = "Aplatizați după completare" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obl" requiredFieldsError = "Completați toate câmpurile obligatorii" rescanFields = "Rescanați câmpurile" @@ -4795,9 +4863,6 @@ title = "Imagine în PDF" [imageToPdf] tags = "conversie,img,jpg,poză,fotografie" -[infoBanner] -dismiss = "Închideți" - [invite] acceptError = "Crearea contului a eșuat" accountFor = "Se creează cont pentru" @@ -4979,6 +5044,34 @@ uploadSuccess = "Încărcare reușită!" uploadSuccessMessage = "Imaginile au fost transferate." validating = "Se validează sesiunea..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Scanați pentru a încărca fotografii. Imaginile se convertesc automat în PDF." descriptionNoConvert = "Scanați pentru a încărca fotografii de pe dispozitivul mobil." @@ -4997,8 +5090,13 @@ title = "Încărcare de pe mobil" tags = "Instrument Multiplu,Operație multiplă,UI,clic tragere,front end,client side" title = "Instrument PDF multiplu" -[navbar] -search = "Căutare" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Autentificarea nu a reușit. Puteți închide această fereastră și să încercați din nou." @@ -5127,6 +5225,7 @@ activeFiles = "Vizualizarea Fișiere active arată toate PDF-ur allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Închide" cropSettings = "Acum că am selectat fișierul pe care dorim să-l decupăm, putem configura instrumentul Decupare pentru a alege zona la care vrem să decupăm PDF-ul." +dialogLabel = "Onboarding" fileCheckbox = "Un clic pe unul dintre fișiere îl selectează pentru procesare. Puteți selecta mai multe fișiere pentru operațiuni în lot." fileReplacement = "Fișierul modificat va înlocui automat fișierul original în Bancul de lucru, permițându-vă să îl treceți ușor prin mai multe instrumente." filesButton = "Butonul Fișiere din bara de acces rapid vă permite să încărcați PDF-uri pe care să folosiți instrumentele." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF către PDF/A" tags = "arhivă,termen-lung,standard,conversie,stocare,conservare" title = "PDF către PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "tipărire,standard,conversie,producție,pregătire pentru tipar,arhivă" title = "PDF în PDF/X" @@ -6045,6 +6151,7 @@ small = "500 de credite" xsmall = "100 de credite" [plan.availablePlans] +currency = "Billing currency" subtitle = "Alegeți planul care se potrivește nevoilor dvs." title = "Planuri disponibile" @@ -6624,6 +6731,7 @@ reachedTitle = "Limita lunară de cheltuieli a fost atinsă" title = "Nu s-a putut deschide portalul Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "din {{allowance}} PDF-uri gratuite folosite" capSuffix_other = "din {{allowance}} PDF-uri gratuite folosite" eyebrow = "Perioadă de încercare Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Trimiteți prin email un document sau o notificare când rulează o politică." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Încărcați documentele procesate în Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDK-uri oficiale" beta = "Beta" deprecated = "Depreciat" -[portal.docs.search] -empty = "Nicio documentație potrivită" -placeholder = "Căutați în documentație" -results = "{{count}} rezultate" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Capabilități incluse, denumite, pe care agentul tău le invocă drept un singur instrument. Fiecare skill este un lanț determinist de operațiuni, cu evals atașate." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Niciun document nu corespunde acestui filtru." rowActions = "Acțiuni rând" sensitiveLabel = "Sensibil" -sensitiveTitle = "Sensibil — acces necesar" [portal.documents.table.columns] action = "Pipeline / Acțiune" document = "Document" +labels = "Labels" product = "Produs" status = "Stare" time = "Timp" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Văzut ultima dată" region = "Regiune" status = "Stare" +target = "Target" version = "Versiune" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Această vizualizare a întâmpinat o eroare neașteptată. Înce retry = "Încercați din nou" title = "Ceva nu a mers bine pe această pagină" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Înapoi" body = "Descărcați aplicația desktop sau găzduiți serverul self-hosted." @@ -7380,11 +7560,6 @@ cancel = "Anulați" confirm = "Revocați cheia" title = "Revocați cheia API" -[portal.infrastructure.attestationLabel] -attested = "Atestat" -inScope = "În domeniu" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtrați evenimentele de audit după categorie" heading = "Jurnale de audit" @@ -7456,11 +7631,6 @@ info = "Info" success = "Succes" warning = "Avertisment" -[portal.infrastructure.certLabel] -certified = "Certificat" -inProgress = "În desfășurare" -notStarted = "Neînceput" - [portal.infrastructure.createKey] cancel = "Anulați" createKey = "Creați cheie" @@ -7474,238 +7644,10 @@ subtitleCreated = "Copiați acest secret acum — nu va mai fi afișat." title = "Creați cheie API" titleCreated = "Cheie creată" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "În coadă" -rolledBack = "Revenit la versiunea anterioară" -rolling = "În curs de lansare" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Implementat de" -environment = "Mediu" -product = "Produs" -status = "Stare" -version = "Versiune" -when = "Când" - -[portal.infrastructure.deployments.recent] -heading = "Implementări recente" -subheading = "Cele mai recente lansări pe produse și medii." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instanțe" -latency = "Latență" -load = "Încărcare" -p99 = "P99" -region = "Regiune" -status = "Stare" -throughput = "Debit" -uptime = "Timp de funcționare" -version = "Versiune" - -[portal.infrastructure.deployments.regions] -heading = "Regiuni" -subheading = "Sănătate în timp real pentru fiecare regiune Stirling implementată — latență, încărcare și versiune de lansare." - -[portal.infrastructure.deployments.regions.empty] -description = "Regiunile implementate apar aici după ce spațiul dvs. de lucru este provisionat." -title = "Nu există regiuni implementate" - [portal.infrastructure.keyLabel] active = "Activă" revoked = "Revocată" -[portal.infrastructure.modelLabel] -active = "Activ" -degraded = "Degradat" -disabled = "Dezactivat" - -[portal.infrastructure.models] -heading = "Modele" -msValue = "{{value}} ms" -subheading = "Catalogul de modele și rutarea care susțin procesarea documentelor în spațiul dvs. de lucru." - -[portal.infrastructure.models.byom] -description = "Înregistrați un model on-prem sau self-hosted și fixați-l într-o regiune pentru procesare cu rezidență a datelor." -title = "Aduceți propriul model" - -[portal.infrastructure.models.catalogue] -heading = "Catalog" -sub = "Modele gestionate disponibile pentru spațiul dvs. de lucru, cu latență și cost în timp real." -subEnterprise = "Modele gestionate, proprii și on-prem — cu fixare per regiune disponibilă." - -[portal.infrastructure.models.catalogue.empty] -description = "Modelele din catalogul spațiului dvs. de lucru apar aici." -title = "Nu există modele disponibile" - -[portal.infrastructure.models.columns] -cost = "Cost" -latency = "Latență" -load = "Încărcare" -model = "Model" -status = "Stare" -type = "Tip" -version = "Versiune" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/apel" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Modele active" -avgLatency = "Latență medie" -included = "Inclus" -monthlySpend = "Cheltuială lunară pentru modele" - -[portal.infrastructure.models.routing] -empty = "Nu sunt configurate reguli de rutare." -heading = "Reguli de rutare" -sub = "Ce model gestionează fiecare operațiune. Valoarea implicită se aplică atunci când nu se potrivește nicio regulă mai restrânsă." -subLocked = "Rutați operațiunile către modele specifice — disponibil în planurile plătite." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Actualizați la Pro pentru a controla ce model gestionează fiecare operațiune și tip de document." -title = "Rutarea modelelor este o funcție plătită" - -[portal.infrastructure.models.routingColumns] -default = "Implicit" -docType = "Tip document" -modelForAria = "Model pentru {{operation}}" -operation = "Operațiune" -routedTo = "Rutat către" - -[portal.infrastructure.modelTypeLabel] -classification = "Clasificare" -extraction = "Extragere" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degradată" -down = "Căzută" -healthy = "Sănătoasă" - -[portal.infrastructure.security.access.byok] -description = "Furnizați o cheie din propriul KMS. Stirling criptează cu ea, dar poate încă citi." -label = "Aduceți propria cheie (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Cheile nu părăsesc niciodată KMS-ul dvs. Stirling deține doar ciphertext." -label = "Păstrați propria cheie (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling gestionează cheile de criptare. Cel mai simplu — zero operațiuni cu chei din partea dvs." -label = "Chei deținute de Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Politică de acces la documente" -subheading = "Controlează cine poate decripta documentele procesate în repaus." - -[portal.infrastructure.security.attestations] -heading = "Atestări de conformitate" -noReport = "Niciun raport disponibil" -subheading = "Postură de audit cadru cu cadru, cu rapoarte disponibile pentru controalele atestate." -viewReport = "Vizualizați raportul →" - -[portal.infrastructure.security.compliance] -heading = "Conformitate" -subheading = "Atestări și certificări care acoperă platforma Stirling." - -[portal.infrastructure.security.empty] -description = "Configurația de securitate a spațiului dvs. de lucru va apărea aici." -title = "Postura de securitate indisponibilă" - -[portal.infrastructure.security.hyokBanner] -description = "Cu HYOK, cheile de criptare nu părăsesc niciodată KMS-ul dvs. Stirling stochează și procesează doar ciphertext pe care îl puteți revoca oricând." -title = "Stirling nu poate decripta documentele dvs." - -[portal.infrastructure.security.ipAllowlist] -empty = "Nu sunt configurate intervale IP — toate IP-urile sunt permise." -heading = "Listă de permisiuni IP" -sub = "Accesul API este restricționat la aceste intervale CIDR." -subLocked = "Restricționați accesul API la intervale IP cunoscute — disponibil în planurile plătite." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Actualizați la Pro pentru a restricționa accesul API la anumite rețele." -title = "Lista de permisiuni IP este o funcție plătită" - -[portal.infrastructure.security.ipColumns] -added = "Adăugat" -addedBy = "Adăugat de" -cidr = "CIDR" -label = "Etichetă" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritm" -heading = "Gestionarea cheilor de criptare" -keyId = "Identificator cheie" -lastRotated = "Rotită ultima dată" -rotateKey = "Rotiți cheia" -rotationPolicy = "Politică de rotație" -subheading = "Custodia cheilor care criptează documentele în repaus — cine poate decripta și cum se rotesc cheile." - -[portal.infrastructure.security.managedBanner] -description = "Custodia bring-your-own-key (BYOK) și hold-your-own-key (HYOK) este disponibilă în Enterprise. Actualizați pentru a furniza chei din propriul KMS." -title = "Cheile sunt gestionate de Stirling în planul dvs." - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asia Pacific" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · limită de date GDPR" -label = "Uniunea Europeană" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Statele Unite" - -[portal.infrastructure.security.residencyHeader] -heading = "Rezidența datelor" -subheading = "Unde sunt stocate și procesate documentele." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} utilizat" - -[portal.infrastructure.storage.empty] -description = "Stocarea conectată și utilizarea apar aici." -title = "Nu este configurată nicio stocare" - -[portal.infrastructure.storage.lifecycle] -active = "Activ" -activeRange = "0–{{value}}d" -archived = "Arhivat" -coldStorage = "stocare rece" -deleted = "Șters" -never = "niciodată" -purged = "epurat" - -[portal.infrastructure.storage.providers] -connect = "Conectați" -connected = "Conectat" -heading = "Furnizori conectați" -subheading = "Unde sunt scrise artefactele procesate." - -[portal.infrastructure.storage.retention] -heading = "Păstrare" -subheading = "Cât timp sunt păstrate artefactele înainte de ștergerea prin ciclu de viață." -windowLabel = "Fereastră implicită de păstrare" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} zi" -days_other = "{{count}} zile" -never = "Nu ștergeți niciodată" - -[portal.infrastructure.storage.totalUsage] -heading = "Utilizare totală" -progressLabel = "Stocare utilizată" -subheading = "Stocare consumată pe toți furnizorii conectați." - [portal.infrastructure.tabs] apiKeys = "Chei API" audit = "Jurnale de audit" @@ -7715,11 +7657,9 @@ security = "Securitate" storage = "Stocare" [portal.integrations] -addAnother = "Adăugați alta" availableHeading = "Disponibile" comingSoonHeading = "În curând" connect = "Conectați" -connectedHeading = "Conectate" connectionCount_one = "{{count}} conexiune" connectionCount_other = "{{count}} conexiuni" customApi = "API personalizat" @@ -7737,6 +7677,10 @@ security = "Securitate" signing = "Semnare" storage = "Stocare" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Conectat" @@ -7767,7 +7711,6 @@ integrations = "Integrări" pipelines = "Pipeline-uri" policies = "Politici" procurement = "Achiziții" -settings = "Setări" sources = "Surse" usage = "Utilizare și facturare" users = "Utilizatori" @@ -7784,7 +7727,7 @@ title = "Pipeline-uri" newPipeline = "Pipeline nou" [portal.pipelines.builder] -addStep = "Adăugați instrument" +activate = "Activate" back = "Înapoi la pipeline-uri" cannotFollow = "Nu poate prelua {{produced}}" chooseAccount = "Alegeți un cont" @@ -7792,28 +7735,38 @@ chooseDestination = "Alegeți o destinație" chooseOperation = "Alegeți ce face acest pas" chooseSource = "Alegeți o sursă" discard = "Renunțați la modificări" -enabled = "Activat" inputs = "Intrare" inputSource = "Sursă de intrare" inputTrigger = "Declanșator" keepEditing = "Continuați editarea" +moreActions = "More actions" needsConfiguring = "Necesită configurare" -needsUpload = "Necesită un fișier încărcat" -noSources = "Nu există surse conectate încă. Creați una pentru a o folosi ca intrare." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Niciun instrument nu corespunde căutării dvs." -pipelineSettings = "Setări pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Căutați instrumente" -selectToolBody = "Adăugați un instrument pentru a construi pipeline-ul." -selectToolTitle = "Nu există încă instrumente" sendToSystem = "Trimiteți către alt sistem" stepsIncompatible = "Acești pași nu pot rula pe ceea ce produce pasul lor anterior: {{tools}}." stepsNeedSetup = "Acești pași necesită încă configurare înainte de salvare: {{tools}}." -toolSettings = "Setări instrument" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Operațiune nerecunoscută, păstrată ca atare." unsavedBody = "Aveți modificări nesalvate. Salvați-le înainte de a pleca sau renunțați la ele?" unsavedTitle = "Modificări nesalvate" -uploadUnsupported = "Fișierele încărcate nu sunt încă acceptate în pipeline-uri, deci acești pași nu pot fi salvați: {{tools}}." usesDefaults = "Rulează cu setările implicite" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Combină fiecare fișier din pasul anterior" @@ -7825,31 +7778,36 @@ undeclared-operation = "Nu se poate verifica ce acceptă acest pas" [portal.pipelines.composer] addTool = "Adăugați instrument" -cancel = "Anulați" -chainEmpty = "Adăugați un instrument pentru a începe să construiți pipeline-ul." create = "Creați pipeline" +createPaused = "Create paused" editingUnsupported = "Afișarea acestor parametri ai instrumentului pentru editare nu este încă acceptată." -moveDown = "Mutați în jos" -moveUp = "Mutați în sus" +editSource = "Edit source" name = "Nume" namePlaceholder = "de ex. Scanare de redactare" noToolSettings = "Acest instrument nu are setări configurabile." -operations_one = "Operațiune ({{count}})" -operations_other = "Operațiuni ({{count}})" output = "Ieșire" -removeStep = "Eliminați operațiunea" save = "Salvați modificările" scheduleEvery = "Rulați la fiecare" -sources = "Surse" -sourcesLoading = "Se încarcă sursele..." trigger = "Declanșator" triggerManual = "Doar manual" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "zile" hours = "ore" minutes = "minute" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Ștergeți „{{name}}”? Această acțiune nu poate fi anulată." cancel = "Anulați" @@ -7867,6 +7825,37 @@ connectSource = "Conectați o sursă" description = "Creați primul pipeline: alegeți sursele peste care rulează, înlănțuiți operațiunile și alegeți unde ajunge ieșirea." title = "Nu există încă pipeline-uri" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Active" paused = "În pauză" @@ -7879,7 +7868,6 @@ completed_one = "Rulare finalizată." completed_other = "Toate cele {{count}} rulări au fost finalizate." empty = "Nimic de rulat: sursele nu au avut documente de procesat." failed = "Rularea a eșuat: {{error}}" -historyCleared = "Istoric șters. Următoarea rulare reprocesează tot ce se află în prezent în surse." inFlight = "Nimic nou de rulat: documentele sunt încă procesate dintr-o rulare anterioară." parked_one = "Nimic de rulat: {{count}} document a eșuat anterior și este parcat. Remediați cauza, apoi ștergeți istoricul pentru a-l reîncerca." parked_other = "Nimic de rulat: {{count}} documente au eșuat anterior și sunt parcate. Remediați cauza, apoi ștergeți istoricul pentru a le reîncerca." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Surse" status = "Stare" steps = "Pași" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Monitorizare folder" @@ -8209,6 +8198,24 @@ label = "Atașați la o problemă Jira" description = "Trimite documentul procesat ca atașament." label = "Trimiteți documentul prin email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Scrie documentul procesat într-un folder." label = "Încărcați în Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Limită atinsă" subtitle = "Trialul tău gratuit rulează până la {{date}}. Nu este necesar cardul." title = "Trial Enterprise" -[portal.search] -ariaLabel = "Caută" -placeholder = "Caută în Stirling — endpoint-uri, pipeline-uri, documente…" - -[portal.search.empty] -noActionsDescription = "Acțiunile rapide vor apărea aici după ce devin disponibile." -noActionsTitle = "Nicio acțiune rapidă" -noMatches = "Nicio potrivire pentru \"{{query}}\"" -noMatchesDescription = "Încearcă un alt cuvânt cheie sau răsfoiește catalogul." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Anulează" confirm = "Șterge" title = "Ștergi sursa?" -[portal.sources.empty] -description = "Conectează un folder (și, în curând, stocare cloud) astfel încât politicile tale să aibă de unde prelua documente." -title = "Încă nu există surse conectate" - [portal.sources.kpi] inUse = "În uz" total = "Conexiuni" @@ -8785,6 +8778,7 @@ unused = "Nefolosit" documents = "Documente" source = "Sursă" status = "Stare" +type = "Type" usedBy = "Politici" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Politici și confidențialitate" [settings.preferences] title = "Preferințe" -[settings.search] -placeholder = "Căutați în paginile de setări..." - [settings.security] description = "Actualizați parola pentru a vă menține contul în siguranță." title = "Securitate" @@ -10128,6 +10119,7 @@ last = "Ultima pagină" maintainRatio = "Comută menținerea raportului de aspect" next = "Pagina următoare" noSavedSigs = "Nu s-au găsit semnături salvate" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Semnături personale" previous = "Pagina anterioară" redo = "Refă" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Eliminarea fundalului a eșuat" hint = "Încărcați o imagine PNG sau JPG a semnăturii dvs." label = "Încarcă imaginea semnăturii" placeholder = "Selectați fișierul imagine" +previewAlt = "Current image signature" processing = "Se procesează imaginea..." removeBackground = "Elimină fundalul alb (transparentizează)" @@ -10179,6 +10172,17 @@ saved = "Selectează o semnătură salvată mai sus, apoi fă clic oriunde pe PD text = "După ce introduceți numele mai sus, faceți clic oriunde pe PDF pentru a plasa semnătura." title = "Cum să adăugați semnătura" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Mută semnătura" pause = "Pauză plasare" @@ -10625,6 +10629,23 @@ title = "Încărcați pe server" updateButton = "Actualizați pe server" uploadButton = "Încărcați pe server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Înapoi" pdfTools = "Instrumente PDF" placeholder = "Alegeți un instrument pentru a începe" premiumFeature = "Funcție premium:" -searchTools = "Caută instrumente" toolsHeader = "Instrumente" viewAllTools = "Vedeți toate instrumentele" @@ -10822,6 +10842,9 @@ removal = "Eliminare" signing = "Semnare" verification = "Verificare" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nu s-au găsit instrumente" noTools = "Niciun instrument disponibil" @@ -10979,6 +11002,13 @@ approver = "Aprobă politica" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Anulați invitația către {{email}}? Nu va putea să se alăture cu linkul actual." cancelInviteTitle = "Anulează invitația" @@ -10998,10 +11028,8 @@ title = "Încă nu există membri" addToTeam = "Adaugă în echipă" guestCount = "{{count}} invitat" guests = "Invitați" -guestsDesc = "Colaboratori externi, limitați la ce ai partajat. Doar Editor." ledBy = "condusă de {{owner}}" org = "Organizație" -orgDesc = "Proprietari cu autoritate la nivel de org și aprobare de politici" owners = "{{count}} proprietar" team = "Echipa {{name}}" teamMeta = "{{count}} persoane" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Invitat de {{who}}" cancel = "Anulează" -count = "{{count}} în așteptare" -desc = "Persoanele invitate care nu s-au alăturat încă. Ocupă un loc până când acceptă." expiresInDays_one = "Expiră peste {{count}} zi" expiresInDays_other = "Expiră peste {{count}} zile" expiresToday = "Expiră astăzi" title = "Invitații în așteptare" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Ceva nu a mers bine la conectarea cu backend-ul sau nu aveți acces. Încercați din nou." title = "Nu s-au putut încărca membrii" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Caută în atașamente" title = "Atașamente" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Titlul marcajului" bookmarkTitleRequired = "Titlul marcajului este obligatoriu" closeSidebar = "Închide bara laterală a marcajelor" collapseAll = "Restrânge toate marcajele" +empty = "No bookmarks in this document" expandAll = "Extinde toate marcajele" searchPlaceholder = "Caută marcaje" +title = "Bookmarks" [viewer.comments] addComment = "Adăugați comentariu" @@ -11264,10 +11297,12 @@ locateAnnotation = "Localizați în document" moreActions = "Mai multe acțiuni" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Pagina {{page}}" placingHint = "Faceți clic pe o pagină pentru a plasa… (anulare)" removeCommentOnly = "Eliminați doar comentariul" saveReply = "Salvați răspunsul" +searchPlaceholder = "Search comments" title = "Comentarii" typeComment = "Comentariu" typeInsertText = "Inserare text" @@ -11290,6 +11325,7 @@ unsavedDesc = "Ai modificări nesalvate" closeSidebar = "Închide bara laterală a straturilor" hideAll = "Ascunde toate straturile" showAll = "Afișează toate straturile" +title = "Layers" [viewer.link] delete = "Ștergeți linkul" @@ -11646,6 +11682,7 @@ exitRedaction = "Ieși din modul de redactare" exportAll = "Exportă PDF" exportSelected = "Exportă paginile selectate" formFill = "Completează formularul" +hideToolbar = "Hide toolbar" multiTool = "Instrument multiplu" panMode = "Mod panoramare" print = "Tipărește PDF" @@ -11666,6 +11703,7 @@ selectAll = "Selectează tot" selectByNumber = "Selectează după numerele paginilor" selectLanguage = "Selectează limba" share = "Partajează" +showToolbar = "Show toolbar" toggleAnnotations = "Comută vizibilitatea adnotărilor" toggleAttachments = "Comută atașamentele" toggleBookmarks = "Comută semnele de carte" diff --git a/frontend/editor/public/locales/ru-RU/translation.toml b/frontend/editor/public/locales/ru-RU/translation.toml index 71926b7be7..9f6321bc64 100644 --- a/frontend/editor/public/locales/ru-RU/translation.toml +++ b/frontend/editor/public/locales/ru-RU/translation.toml @@ -1,7 +1,6 @@ alphabet = "Алфавит" apply = "Применить" applyAndContinue = "Применить и продолжить" -areYouSure = "Вы уверены, что хотите уйти?" back = "Назад" black = "Черный" blue = "Синий" @@ -106,6 +105,7 @@ undoSuccess = "Операция успешно отменена" unknown = "Неизвестно" unpin = "Открепить файл (заменить после выполнения инструмента)" unsavedChanges = "У вас есть несохранённые изменения в вашем PDF. Что вы хотите сделать?" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Несохранённые изменения" unsupported = "Не поддерживается" username = "Имя пользователя" @@ -1879,6 +1879,9 @@ width = "Ширина" [app] description = "Бесплатная альтернатива Adobe Acrobat (10M+ загрузок)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Преобразовать в PDF/A-3b" convertToPdfA3bDescription = "Создаёт архивный PDF со встроенными вложениями" @@ -2933,12 +2936,16 @@ text = "Чтобы сделать эти разрешения неизменяе [chat.actions] copy = "Копировать сообщение" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Параметры агента Stirling" +assistant = "AI Assistant" clearChat = "Очистить чат" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Эта операция использует ваши облачны tooltip = "Выбрать цвет с экрана" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Выберите цвет" [common] +actions = "Actions" back = "Назад" cancel = "Отмена" close = "Закрыть" +codeSample = "Code sample" collapse = "Свернуть" confirm = "Подтвердить" continue = "Продолжить" copied = "Скопировано!" copy = "Копировать" done = "Готово" +enterValue = "Enter value" error = "Ошибка" expand = "Развернуть" loading = "Загрузка..." next = "Далее" +open = "Open" preview = "Предпросмотр" previous = "Предыдущий" refresh = "Обновить" remaining = "Осталось" retry = "Повторить" save = "Сохранить" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Очистить выбранное" @@ -3185,6 +3199,7 @@ title = "Метод сжатия" [compress.settings] desiredSize = "Желаемый размер файла" desiredSizePlaceholder = "Введите размер" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Сжатие — простой способ уменьшить размер файла. Выберите «Размер файла», чтобы указать целевой размер, а мы подберём качество. Выберите «Качество», чтобы задать силу сжатия вручную." @@ -3387,6 +3402,24 @@ pdfOptions = "Параметры PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Сначала выберите исходный формат" settings = "Настройки" single = "Один" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Поиск..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Изменить" @@ -3824,11 +3861,13 @@ shareSelected = "Поделиться выбранными" sharing = "Общий доступ" showAll = "Показать все" showHistory = "Показать историю" +sortBy = "Sort files" sortByDate = "Сортировать по дате" sortByName = "Сортировать по имени" sortBySize = "Сортировать по размеру" storage = "Хранилище" storageState = "Хранилище" +storageUsed = "Storage used" synced = "Синхронизировано" title = "Загрузить PDF-файлы" toolChain = "Применённые инструменты" @@ -3874,8 +3913,12 @@ addFiles = "Добавить файлы" addingFiles = "Добавление файлов…" collapse = "Свернуть боковую панель" customizeGroups = "Настроить группы" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Откройте файлы, чтобы начать" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Развернуть боковую панель" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive не настроен" @@ -3888,15 +3931,17 @@ openFromComputer = "Открыть с компьютера" openSettings = "Открыть настройки" other = "Другое" recent = "Недавние" -search = "Поиск" -searchPlaceholder = "Поиск файлов..." viewAll = "Показать все {{count}} файлов" [fileSidebar.fileItem] closeViewer = "Закрыть просмотрщик" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Открыть в просмотрщике" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Сбросить к значениям по умолчанию" subtitle = "Группируйте файлы по родительским категориям. Добавляйте существующие или новые метки в категорию, переименовывайте её или создавайте свою. Файлы, не входящие ни в одну из ваших категорий, отображаются в разделе «Другое»." title = "Категории боковой панели" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Добавить в рабочую область" addToWorkspaceCount = "Добавить {{count}} в рабочую область" @@ -3918,7 +3971,6 @@ backToMyFiles = "Назад к Моим файлам" breadcrumbs = "Путь к папке" cancel = "Отмена" classification = "Классификация" -clearSearch = "Очистить поиск" clearSelection = "Очистить выбор" closeDetails = "Закрыть сведения" create = "Создать" @@ -3950,6 +4002,7 @@ downloadAll = "Скачать все" downloadVersion = "Скачать эту версию" dropOverlay = "Перетащите файлы для загрузки" dropOverlaySub = "Файлы сначала попадают в Локальные. Используйте «Переместить в» или «Сохранить в облако», чтобы разместить их в папке." +duplicate = "Duplicate" file = "Файл" fileInfo = "File info" fileMenu = "Действия с файлом" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Изменить размер дерева папок (ст save = "Сохранить" saveToServer = "Сохранить на сервер" saveToServerDisabledHint = "Сохранение на сервер не включено на этом сервере. Попросите администратора включить его." -search = "Поиск" -searchPlaceholder = "Искать в этой папке и подпапках" selectAll = "Выбрать всё" selectAllHint = "Нажмите, чтобы выбрать всё. Совет: удерживайте Ctrl (или Cmd), чтобы добавлять файлы по одному, Shift — для выбора диапазона." selectedCount = "Выбрано: {{count}}" @@ -4044,6 +4095,10 @@ title = "Пока нет облачных файлов" hint = "Файлы, сохранённые без загрузки, остаются здесь. Перетащите файл, чтобы добавить его." title = "Нет локальных файлов" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Здесь будут отображаться файлы, которые вы открывали или редактировали." title = "Пока ничего не изменено" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Не удалось удалить папку." deleteFolderFailedDetail = "Не удалось удалить папку: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Не удалось обновить оформление папки." folderAppearanceFailedDetail = "Не удалось обновить оформление папки: {{message}}" moveFilesFailed = "Не удалось переместить файлы." @@ -4113,6 +4170,11 @@ localHint = "Хранится только в этом браузере" shared = "Общий доступ" sharedHint = "Предоставлен по ссылке" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Сначала старые" modifiedDesc = "Сначала новые" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "В хранилище нет файлов. Сначал open = "Открыть" openFile = "Открыть файл" openFiles = "Открыть файлы" +selectFile = "Select {{name}}" selectFromStorage = "Выбрать из хранилища" upload = "Загрузить" uploadFile = "Загрузить файл" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Все сохранено" +analyzingFields = "Analysing form fields..." extractCsvError = "Не удалось извлечь CSV" extractXlsxError = "Не удалось извлечь XLSX" +filled = "filled" flattenAfterFilling = "Сплющить после заполнения" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "обяз." requiredFieldsError = "Заполните все обязательные поля" rescanFields = "Пересканировать поля" @@ -4795,9 +4863,6 @@ title = "Изображение в PDF" [imageToPdf] tags = "конвертация,изображение,jpg,картинка,фото" -[infoBanner] -dismiss = "Закрыть" - [invite] acceptError = "Не удалось создать учетную запись" accountFor = "Создание учетной записи для" @@ -4979,6 +5044,34 @@ uploadSuccess = "Загрузка выполнена!" uploadSuccessMessage = "Ваши изображения переданы." validating = "Проверка сеанса..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Сканируйте для загрузки фото. Изображения автоматически преобразуются в PDF." descriptionNoConvert = "Сканируйте, чтобы загрузить фото с мобильного устройства." @@ -4997,8 +5090,13 @@ title = "Загрузка с мобильного" tags = "Мультиинструмент,Многооперационный,UI,перетаскивание,клиентская часть,интерактивный" title = "Мультиинструмент PDF" -[navbar] -search = "Поиск" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Аутентификация не выполнена. Закройте окно и попробуйте снова." @@ -5127,6 +5225,7 @@ activeFiles = "Вид Активные файлы показы allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Закрыть" cropSettings = "Теперь, когда мы выбрали файл для обрезки, можно настроить инструмент «Обрезка», чтобы выбрать область, до которой нужно обрезать PDF." +dialogLabel = "Onboarding" fileCheckbox = "Щелчок по файлу выбирает его для обработки. Можно выбрать несколько файлов для пакетных операций." fileReplacement = "Измененный файл автоматически заменит исходный файл в Рабочей области, что позволит легко прогнать его через другие инструменты." filesButton = "Кнопка Файлы на панели быстрого доступа позволяет загружать PDF, с которыми будут работать инструменты." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF в PDF/A" tags = "архив,долгосрочный,стандарт,конвертация,хранение,сохранение" title = "PDF в PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "печать,стандарт,конвертация,производство,допечатная подготовка,архив" title = "PDF в PDF/X" @@ -6045,6 +6151,7 @@ small = "500 кредитов" xsmall = "100 кредитов" [plan.availablePlans] +currency = "Billing currency" subtitle = "Выберите план, подходящий вашим потребностям" title = "Доступные планы" @@ -6624,6 +6731,7 @@ reachedTitle = "Месячный лимит расходов достигнут" title = "Не удалось открыть портал Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "из {{allowance}} бесплатных PDF использовано" capSuffix_other = "из {{allowance}} бесплатных PDF использовано" eyebrow = "Пробный период Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Отправляйте документ или уведомление по Email, когда выполняется политика." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Сохраняйте обработанные документы в Nextcloud." @@ -7077,11 +7211,6 @@ title = "Официальные SDK" beta = "Beta" deprecated = "Устарело" -[portal.docs.search] -empty = "Подходящих документов нет" -placeholder = "Искать в документации" -results = "{{count}} результатов" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Встроенные именованные возможности, которые ваш агент вызывает как единый инструмент. Каждый skill — детерминированная цепочка операций с привязанными evals." @@ -7180,11 +7309,11 @@ editorAction = "Редактор" empty = "Нет документов, соответствующих этому фильтру." rowActions = "Действия со строкой" sensitiveLabel = "Конфиденциально" -sensitiveTitle = "Конфиденциально — требуется доступ" [portal.documents.table.columns] action = "Pipeline / Действие" document = "Документ" +labels = "Labels" product = "Продукт" status = "Статус" time = "Время" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Последний сеанс" region = "Регион" status = "Статус" +target = "Target" version = "Версия" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "В этом представлении произошла неп retry = "Попробовать еще раз" title = "На этой странице что-то пошло не так" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Назад" body = "Скачайте настольное приложение или самостоятельно разместите сервер." @@ -7380,11 +7560,6 @@ cancel = "Отмена" confirm = "Отозвать ключ" title = "Отозвать API ключ" -[portal.infrastructure.attestationLabel] -attested = "Подтверждено" -inScope = "В области действия" -notApplicable = "Н/Д" - [portal.infrastructure.audit] filterAriaLabel = "Фильтровать события аудита по категории" heading = "Журналы аудита" @@ -7456,11 +7631,6 @@ info = "Информация" success = "Успешно" warning = "Предупреждение" -[portal.infrastructure.certLabel] -certified = "Сертифицировано" -inProgress = "В процессе" -notStarted = "Не начато" - [portal.infrastructure.createKey] cancel = "Отмена" createKey = "Создать ключ" @@ -7474,238 +7644,10 @@ subtitleCreated = "Скопируйте этот секрет сейчас — title = "Создать ключ API" titleCreated = "Ключ создан" -[portal.infrastructure.deployLabel] -live = "Работает" -queued = "В очереди" -rolledBack = "Откат выполнен" -rolling = "Развертывается" - -[portal.infrastructure.deployments] -msValue = "{{value}} мс" -throughputValue = "{{value}}/мин" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Развернул" -environment = "Среда" -product = "Продукт" -status = "Статус" -version = "Версия" -when = "Когда" - -[portal.infrastructure.deployments.recent] -heading = "Недавние развертывания" -subheading = "Последние выпуски по продуктам и средам." - -[portal.infrastructure.deployments.regionColumns] -instances = "Экземпляры" -latency = "Задержка" -load = "Нагрузка" -p99 = "P99" -region = "Регион" -status = "Статус" -throughput = "Пропускная способность" -uptime = "Uptime" -version = "Версия" - -[portal.infrastructure.deployments.regions] -heading = "Регионы" -subheading = "Текущее состояние каждого развернутого региона Stirling — задержка, нагрузка и версия выпуска." - -[portal.infrastructure.deployments.regions.empty] -description = "Развернутые регионы появятся здесь после подготовки вашего рабочего пространства." -title = "Нет развернутых регионов" - [portal.infrastructure.keyLabel] active = "Активен" revoked = "Отозван" -[portal.infrastructure.modelLabel] -active = "Активна" -degraded = "Снижена производительность" -disabled = "Отключена" - -[portal.infrastructure.models] -heading = "Модели" -msValue = "{{value}} мс" -subheading = "Каталог моделей и маршрутизация, обеспечивающие обработку документов во всем вашем рабочем пространстве." - -[portal.infrastructure.models.byom] -description = "Зарегистрируйте локальную или самостоятельно размещенную модель и закрепите ее за регионом для обработки с учетом резидентности данных." -title = "Используйте свою модель" - -[portal.infrastructure.models.catalogue] -heading = "Каталог" -sub = "Управляемые модели, доступные вашему рабочему пространству, с текущей задержкой и стоимостью." -subEnterprise = "Управляемые, собственные и локальные модели — с возможностью закрепления по регионам." - -[portal.infrastructure.models.catalogue.empty] -description = "Модели из каталога вашего рабочего пространства появятся здесь." -title = "Нет доступных моделей" - -[portal.infrastructure.models.columns] -cost = "Стоимость" -latency = "Задержка" -load = "Нагрузка" -model = "Модель" -status = "Статус" -type = "Тип" -version = "Версия" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/вызов" -perThousand = "{{price}}/1 тыс." - -[portal.infrastructure.models.metrics] -activeModels = "Активные модели" -avgLatency = "Средняя задержка" -included = "Включено" -monthlySpend = "Ежемесячные расходы на модели" - -[portal.infrastructure.models.routing] -empty = "Правила маршрутизации не настроены." -heading = "Правила маршрутизации" -sub = "Какая модель обрабатывает каждую операцию. Значение по умолчанию применяется, когда нет более узкого подходящего правила." -subLocked = "Направляйте операции к определенным моделям — доступно на платных планах." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Перейдите на Pro, чтобы управлять тем, какая модель обрабатывает каждую операцию и тип документа." -title = "Маршрутизация моделей — платная функция" - -[portal.infrastructure.models.routingColumns] -default = "По умолчанию" -docType = "Тип документа" -modelForAria = "Модель для {{operation}}" -operation = "Операция" -routedTo = "Направлено в" - -[portal.infrastructure.modelTypeLabel] -classification = "Классификация" -extraction = "Извлечение" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Снижена производительность" -down = "Недоступен" -healthy = "Исправен" - -[portal.infrastructure.security.access.byok] -description = "Предоставьте ключ из собственного KMS. Stirling шифрует им, но все еще может читать." -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Ключи никогда не покидают ваш KMS. Stirling хранит только ciphertext." -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling управляет ключами шифрования. Самый простой вариант — нулевые операции с ключами с вашей стороны." -label = "Ключи хранятся в Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Политика доступа к документам" -subheading = "Управляет тем, кто может расшифровывать обработанные документы в состоянии покоя." - -[portal.infrastructure.security.attestations] -heading = "Подтверждения соответствия" -noReport = "Отчет недоступен" -subheading = "Состояние аудита по каждому фреймворку, с отчетами по подтвержденным контролям." -viewReport = "Просмотреть отчет →" - -[portal.infrastructure.security.compliance] -heading = "Соответствие требованиям" -subheading = "Подтверждения и сертификации, охватывающие платформу Stirling." - -[portal.infrastructure.security.empty] -description = "Конфигурация безопасности вашего рабочего пространства появится здесь." -title = "Состояние безопасности недоступно" - -[portal.infrastructure.security.hyokBanner] -description = "С HYOK ключи шифрования никогда не покидают ваш KMS. Stirling хранит и обрабатывает только ciphertext, который вы можете отозвать в любое время." -title = "Stirling не может расшифровать ваши документы" - -[portal.infrastructure.security.ipAllowlist] -empty = "Диапазоны IP не настроены — разрешены все IP." -heading = "IP allowlist" -sub = "Доступ к API ограничен этими диапазонами CIDR." -subLocked = "Ограничьте доступ к API известными диапазонами IP — доступно на платных планах." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Перейдите на Pro, чтобы ограничить доступ к API определенными сетями." -title = "IP allowlisting — платная функция" - -[portal.infrastructure.security.ipColumns] -added = "Добавлено" -addedBy = "Добавил" -cidr = "CIDR" -label = "Метка" - -[portal.infrastructure.security.keyManagement] -algorithm = "Алгоритм" -heading = "Управление ключами шифрования" -keyId = "Идентификатор ключа" -lastRotated = "Последняя смена" -rotateKey = "Сменить ключ" -rotationPolicy = "Политика смены" -subheading = "Хранение ключей, шифрующих документы в состоянии покоя — кто может расшифровать и как меняются ключи." - -[portal.infrastructure.security.managedBanner] -description = "Хранение ключей Bring-your-own-key (BYOK) и hold-your-own-key (HYOK) доступно в Enterprise. Улучшите план, чтобы предоставлять ключи из собственного KMS." -title = "На вашем плане ключами управляет Stirling" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Азиатско-Тихоокеанский регион" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · граница данных GDPR" -label = "Европейский союз" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "США" - -[portal.infrastructure.security.residencyHeader] -heading = "Резидентность данных" -subheading = "Где документы хранятся и обрабатываются." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "Использовано {{value}}" - -[portal.infrastructure.storage.empty] -description = "Подключенное хранилище и использование появятся здесь." -title = "Хранилище не настроено" - -[portal.infrastructure.storage.lifecycle] -active = "Активно" -activeRange = "0–{{value}}d" -archived = "В архиве" -coldStorage = "холодное хранилище" -deleted = "Удалено" -never = "никогда" -purged = "очищено" - -[portal.infrastructure.storage.providers] -connect = "Подключить" -connected = "Подключено" -heading = "Подключенные провайдеры" -subheading = "Куда записываются обработанные артефакты." - -[portal.infrastructure.storage.retention] -heading = "Хранение" -subheading = "Как долго артефакты хранятся перед удалением по жизненному циклу." -windowLabel = "Окно хранения по умолчанию" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} день" -days_other = "{{count}} дней" -never = "Никогда не удалять" - -[portal.infrastructure.storage.totalUsage] -heading = "Общее использование" -progressLabel = "Использовано хранилища" -subheading = "Хранилище, использованное во всех подключенных провайдерах." - [portal.infrastructure.tabs] apiKeys = "Ключи API" audit = "Журналы аудита" @@ -7715,11 +7657,9 @@ security = "Безопасность" storage = "Хранилище" [portal.integrations] -addAnother = "Добавить еще" availableHeading = "Доступно" comingSoonHeading = "Скоро" connect = "Подключить" -connectedHeading = "Подключено" connectionCount_one = "{{count}} подключение" connectionCount_other = "{{count}} подключений" customApi = "Пользовательский API" @@ -7737,6 +7677,10 @@ security = "Безопасность" signing = "Подписание" storage = "Хранилище" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Подключено" @@ -7767,7 +7711,6 @@ integrations = "Интеграции" pipelines = "Pipelines" policies = "Политики" procurement = "Закупки" -settings = "Настройки" sources = "Источники" usage = "Использование и оплата" users = "Пользователи" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Новый pipeline" [portal.pipelines.builder] -addStep = "Добавить инструмент" +activate = "Activate" back = "Назад к pipelines" cannotFollow = "Не удается принять {{produced}}" chooseAccount = "Выберите учетную запись" @@ -7792,28 +7735,38 @@ chooseDestination = "Выберите место назначения" chooseOperation = "Выберите, что делает этот шаг" chooseSource = "Выберите источник" discard = "Отменить изменения" -enabled = "Включено" inputs = "Ввод" inputSource = "Источник ввода" inputTrigger = "Триггер" keepEditing = "Продолжить редактирование" +moreActions = "More actions" needsConfiguring = "Требуется настройка" -needsUpload = "Требуется загруженный файл" -noSources = "Источники еще не подключены. Создайте источник, чтобы использовать его как ввод." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Нет инструментов, соответствующих вашему поиску." -pipelineSettings = "Настройки pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Искать инструменты" -selectToolBody = "Добавьте инструмент, чтобы создать свой pipeline." -selectToolTitle = "Инструментов пока нет" sendToSystem = "Отправить в другую систему" stepsIncompatible = "Эти шаги не могут работать с тем, что производит предыдущий шаг: {{tools}}." stepsNeedSetup = "Эти шаги все еще нужно настроить перед сохранением: {{tools}}." -toolSettings = "Настройки инструмента" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Нераспознанная операция, сохранена как есть." unsavedBody = "У вас есть несохраненные изменения. Сохранить их перед выходом или отменить?" unsavedTitle = "Несохраненные изменения" -uploadUnsupported = "Загруженные файлы пока не поддерживаются в pipelines, поэтому эти шаги нельзя сохранить: {{tools}}." usesDefaults = "Запускается с настройками по умолчанию" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Объединяет каждый файл из предыдущего шага" @@ -7825,31 +7778,36 @@ undeclared-operation = "Не удается проверить, что прин [portal.pipelines.composer] addTool = "Добавить инструмент" -cancel = "Отмена" -chainEmpty = "Добавьте инструмент, чтобы начать создавать свой pipeline." create = "Создать pipeline" +createPaused = "Create paused" editingUnsupported = "Отображение этих параметров инструмента для редактирования пока не поддерживается." -moveDown = "Переместить вниз" -moveUp = "Переместить вверх" +editSource = "Edit source" name = "Имя" namePlaceholder = "например, проверка редактирования" noToolSettings = "У этого инструмента нет настраиваемых параметров." -operations_one = "Операция ({{count}})" -operations_other = "Операции ({{count}})" output = "Вывод" -removeStep = "Удалить операцию" save = "Сохранить изменения" scheduleEvery = "Запускать каждые" -sources = "Источники" -sourcesLoading = "Загрузка источников..." trigger = "Триггер" triggerManual = "Только вручную" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "дней" hours = "часов" minutes = "минут" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Удалить \"{{name}}\"? Это действие нельзя отменить." cancel = "Отмена" @@ -7867,6 +7825,37 @@ connectSource = "Подключить источник" description = "Создайте первый pipeline: выберите источники, над которыми он запускается, свяжите операции и выберите, куда отправлять вывод." title = "Pipelines пока нет" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Активные" paused = "Приостановленные" @@ -7879,7 +7868,6 @@ completed_one = "Запуск завершен." completed_other = "Все {{count}} запусков завершены." empty = "Нечего запускать: в источниках не было документов для обработки." failed = "Запуск не удался: {{error}}" -historyCleared = "История очищена. При следующем запуске будет заново обработано все, что сейчас находится в источниках." inFlight = "Нет ничего нового для запуска: документы все еще обрабатываются после предыдущего запуска." parked_one = "Нечего запускать: {{count}} документ ранее завершился с ошибкой и отложен. Устраните причину, затем очистите историю, чтобы повторить попытку." parked_other = "Нечего запускать: {{count}} документов ранее завершились с ошибкой и отложены. Устраните причину, затем очистите историю, чтобы повторить попытку." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Источники" status = "Статус" steps = "Шаги" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Отслеживание папки" @@ -8209,6 +8198,24 @@ label = "Прикрепить к задаче Jira" description = "Отправляет обработанный документ как вложение." label = "Отправить документ по email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Записывает обработанный документ в папку." label = "Загрузить в Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Лимит исчерпан" subtitle = "Ваш бесплатный пробный период действует до {{date}}. Карта не требуется." title = "Пробный период Enterprise" -[portal.search] -ariaLabel = "Поиск" -placeholder = "Поиск в Stirling — endpoints, конвейеры, документы…" - -[portal.search.empty] -noActionsDescription = "Быстрые действия появятся здесь, когда станут доступны." -noActionsTitle = "Нет быстрых действий" -noMatches = "Нет совпадений для «{{query}}»" -noMatchesDescription = "Попробуйте другое ключевое слово или просмотрите каталог." - [portal.settings.groups] admin = "Администратор" @@ -8743,10 +8740,6 @@ cancel = "Отмена" confirm = "Удалить" title = "Удалить источник?" -[portal.sources.empty] -description = "Подключите папку (а скоро и облачное хранилище), чтобы вашим политикам было откуда получать документы." -title = "Источники пока не подключены" - [portal.sources.kpi] inUse = "Используются" total = "Подключения" @@ -8785,6 +8778,7 @@ unused = "Не используется" documents = "Документы" source = "Источник" status = "Статус" +type = "Type" usedBy = "Политики" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Политики и конфиденциальность" [settings.preferences] title = "Настройки" -[settings.search] -placeholder = "Поиск по страницам настроек..." - [settings.security] description = "Обновите пароль, чтобы защитить свою учётную запись." title = "Безопасность" @@ -10128,6 +10119,7 @@ last = "Последняя страница" maintainRatio = "Переключить сохранение пропорций" next = "Следующая страница" noSavedSigs = "Сохраненные подписи не найдены" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Личные подписи" previous = "Предыдущая страница" redo = "Повторить" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Не удалось удалить фон" hint = "Загрузите PNG или JPG с вашей подписью" label = "Загрузить изображение подписи" placeholder = "Выберите файл изображения" +previewAlt = "Current image signature" processing = "Обработка изображения..." removeBackground = "Удалить белый фон (сделать прозрачным)" @@ -10179,6 +10172,17 @@ saved = "Выберите сохранённую подпись выше, зат text = "После ввода вашего имени выше щелкните в любом месте PDF, чтобы поставить подпись." title = "Как добавить подпись" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Переместить подпись" pause = "Пауза размещения" @@ -10625,6 +10629,23 @@ title = "Загрузить на сервер" updateButton = "Обновить на сервере" uploadButton = "Загрузить на сервер" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Вернуться" pdfTools = "Инструменты PDF" placeholder = "Выберите инструмент, чтобы начать" premiumFeature = "Премиум‑функция:" -searchTools = "Поиск инструментов" toolsHeader = "Инструменты" viewAllTools = "Показать все инструменты" @@ -10822,6 +10842,9 @@ removal = "Удаление" signing = "Подписание" verification = "Проверка" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Инструменты не найдены" noTools = "Нет доступных инструментов" @@ -10979,6 +11002,13 @@ approver = "Утверждает политику" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Отменить приглашение для {{email}}? Они не смогут присоединиться по текущей ссылке." cancelInviteTitle = "Отменить приглашение" @@ -10998,10 +11028,8 @@ title = "Участников пока нет" addToTeam = "Добавить в команду" guestCount = "{{count}} гость" guests = "Гости" -guestsDesc = "Внешние участники с доступом только к тому, чем вы поделились. Только Editor." ledBy = "руководит {{owner}}" org = "Организация" -orgDesc = "Владельцы с полномочиями на уровне всей организации и утверждением политик" owners = "{{count}} владельцев" team = "Команда {{name}}" teamMeta = "{{count}} человек" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Пригласил(а) {{who}}" cancel = "Отменить" -count = "{{count}} в ожидании" -desc = "Приглашенные люди, которые еще не присоединились. Они занимают место, пока не примут приглашение." expiresInDays_one = "Истекает через {{count}} день" expiresInDays_other = "Истекает через {{count}} дн." expiresToday = "Истекает сегодня" title = "Ожидающие приглашения" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Что-то пошло не так при обращении к серверной части, или у вас нет доступа. Попробуйте снова." title = "Не удалось загрузить участников" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Поиск вложений" title = "Вложения" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Название закладки" bookmarkTitleRequired = "Название закладки обязательно" closeSidebar = "Закрыть боковую панель закладок" collapseAll = "Свернуть все закладки" +empty = "No bookmarks in this document" expandAll = "Развернуть все закладки" searchPlaceholder = "Поиск закладок" +title = "Bookmarks" [viewer.comments] addComment = "Добавить комментарий" @@ -11264,10 +11297,12 @@ locateAnnotation = "Найти в документе" moreActions = "Другие действия" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Страница {{page}}" placingHint = "Щёлкните по странице, чтобы разместить… (отмена)" removeCommentOnly = "Удалить только комментарий" saveReply = "Сохранить ответ" +searchPlaceholder = "Search comments" title = "Комментарии" typeComment = "Комментарий" typeInsertText = "Вставить текст" @@ -11290,6 +11325,7 @@ unsavedDesc = "У вас есть несохранённые изменения" closeSidebar = "Закрыть боковую панель слоев" hideAll = "Скрыть все слои" showAll = "Показать все слои" +title = "Layers" [viewer.link] delete = "Удалить ссылку" @@ -11646,6 +11682,7 @@ exitRedaction = "Выйти из режима скрытия данных" exportAll = "Экспортировать PDF" exportSelected = "Экспортировать выбранные страницы" formFill = "Заполнить форму" +hideToolbar = "Hide toolbar" multiTool = "Мультиинструмент" panMode = "Режим перемещения" print = "Печать PDF" @@ -11666,6 +11703,7 @@ selectAll = "Выбрать все" selectByNumber = "Выбрать по номерам страниц" selectLanguage = "Выбрать язык" share = "Поделиться" +showToolbar = "Show toolbar" toggleAnnotations = "Показать/скрыть аннотации" toggleAttachments = "Показать/скрыть вложения" toggleBookmarks = "Показать/скрыть закладки" diff --git a/frontend/editor/public/locales/sk-SK/translation.toml b/frontend/editor/public/locales/sk-SK/translation.toml index 22d992b1b7..fc43937505 100644 --- a/frontend/editor/public/locales/sk-SK/translation.toml +++ b/frontend/editor/public/locales/sk-SK/translation.toml @@ -1,7 +1,6 @@ alphabet = "Abeceda" apply = "Použiť" applyAndContinue = "Uložiť a odísť" -areYouSure = "Naozaj chcete odísť?" back = "Späť" black = "Čierna" blue = "Modrá" @@ -106,6 +105,7 @@ undoSuccess = "Operácia bola úspešne vrátená" unknown = "Neznáme" unpin = "Zrušiť pripnutie súboru (nahradiť po spustení nástroja)" unsavedChanges = "Máte neuložené zmeny v PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Neuložené zmeny" unsupported = "Nepodporované" username = "Používateľské meno" @@ -1879,6 +1879,9 @@ width = "Šírka" [app] description = "Bezplatná alternatíva k Adobe Acrobat (10M+ stiahnutí)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Konvertovať na PDF/A-3b" convertToPdfA3bDescription = "Vytvorí archívne PDF s vloženými prílohami" @@ -2933,12 +2936,16 @@ text = "Aby boli tieto povolenia nezmeniteľné, použite nástroj Add Password [chat.actions] copy = "Kopírovať správu" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Možnosti agenta Stirling" +assistant = "AI Assistant" clearChat = "Vyčistiť chat" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Táto operácia použije vaše cloudové kredity" tooltip = "Vybrať farbu z obrazovky" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Vyberte farbu" [common] +actions = "Actions" back = "Späť" cancel = "Zrušiť" close = "Zavrieť" +codeSample = "Code sample" collapse = "Zbaliť" confirm = "Potvrdiť" continue = "Pokračovať" copied = "Skopírované!" copy = "Kopírovať" done = "Hotovo" +enterValue = "Enter value" error = "Chyba" expand = "Rozbaliť" loading = "Načítava sa..." next = "Ďalej" +open = "Open" preview = "Náhľad" previous = "Predchádzajúce" refresh = "Obnoviť" remaining = "Zostáva" retry = "Skúsiť znova" save = "Uložiť" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Vymazať výber" @@ -3185,6 +3199,7 @@ title = "Metóda kompresie" [compress.settings] desiredSize = "Požadovaná veľkosť súboru" desiredSizePlaceholder = "Zadajte veľkosť" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Kompresia je jednoduchý spôsob, ako zmenšiť veľkosť súboru. Zvoľte Veľkosť súboru a zadajte cieľovú veľkosť, pričom kvalitu upravíme za vás. Zvoľte Kvalita a nastavte silu kompresie manuálne." @@ -3387,6 +3402,24 @@ pdfOptions = "Možnosti PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Najprv vyberte zdrojový formát" settings = "Nastavenia" single = "Jeden" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Hľadať..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Upraviť" @@ -3824,11 +3861,13 @@ shareSelected = "Zdieľať vybrané" sharing = "Zdieľanie" showAll = "Zobraziť všetko" showHistory = "Zobraziť históriu" +sortBy = "Sort files" sortByDate = "Zoradiť podľa dátumu" sortByName = "Zoradiť podľa názvu" sortBySize = "Zoradiť podľa veľkosti" storage = "Úložisko" storageState = "Úložisko" +storageUsed = "Storage used" synced = "Synchronizované" title = "Nahrať súbory PDF" toolChain = "Použité nástroje" @@ -3874,8 +3913,12 @@ addFiles = "Pridať súbory" addingFiles = "Pridávajú sa súbory…" collapse = "Zbaliť bočný panel" customizeGroups = "Prispôsobiť skupiny" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Ak chcete začať, otvorte súbory" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Rozbaliť bočný panel" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive nie je nakonfigurovaný" @@ -3888,15 +3931,17 @@ openFromComputer = "Otvoriť z počítača" openSettings = "Otvoriť nastavenia" other = "Ostatné" recent = "Nedávne" -search = "Hľadať" -searchPlaceholder = "Hľadať súbory..." viewAll = "Zobraziť všetkých {{count}} súborov" [fileSidebar.fileItem] closeViewer = "Zavrieť prehliadač" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Otvoriť v prehliadači" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Obnoviť predvolené nastavenia" subtitle = "Zoskupte svoje súbory do nadradených kategórií. Pridajte existujúce alebo nové štítky do kategórie, premenujte ju alebo si vytvorte vlastnú. Súbory, ktoré nie sú v žiadnej z vašich kategórií, sa zobrazia v časti „Ostatné“." title = "Kategórie bočného panela" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Pridať do pracovného priestoru" addToWorkspaceCount = "Pridať {{count}} do pracovného priestoru" @@ -3918,7 +3971,6 @@ backToMyFiles = "Späť na Moje súbory" breadcrumbs = "Cesta k priečinku" cancel = "Zrušiť" classification = "Klasifikácia" -clearSearch = "Vymazať vyhľadávanie" clearSelection = "Zrušiť výber" closeDetails = "Zavrieť podrobnosti" create = "Vytvoriť" @@ -3950,6 +4002,7 @@ downloadAll = "Stiahnuť všetko" downloadVersion = "Stiahnuť túto verziu" dropOverlay = "Pustite súbory sem na nahratie" dropOverlaySub = "Súbory začnú v Lokálne. Použite 'Presunúť do' alebo 'Uložiť do cloudu' na ich usporiadanie do priečinka." +duplicate = "Duplicate" file = "Súbor" fileInfo = "File info" fileMenu = "Akcie súboru" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Zmeniť veľkosť stromu priečinkov (šípkami, Shift pre v save = "Uložiť" saveToServer = "Uložiť na server" saveToServerDisabledHint = "Ukladanie na server nie je na tomto serveri povolené. Požiadajte správcu, aby ho povolil." -search = "Hľadať" -searchPlaceholder = "Hľadať v tomto priečinku a podpriečinkoch" selectAll = "Vybrať všetko" selectAllHint = "Kliknutím vyberiete všetko. Tip: podržte Ctrl (alebo Cmd) na pridávanie súborov po jednom, Shift na výber rozsahu." selectedCount = "Vybrané: {{count}}" @@ -4044,6 +4095,10 @@ title = "Zatiaľ žiadne cloudové súbory" hint = "Súbory uložené bez nahrania zostanú tu. Pretiahnite súbor, aby ste pridali nový." title = "Žiadne iba lokálne súbory" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Súbory, ktoré otvoríte alebo upravíte, sa zobrazia tu." title = "Zatiaľ nič upravené" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Priečinok sa nepodarilo odstrániť." deleteFolderFailedDetail = "Priečinok sa nepodarilo odstrániť: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Nepodarilo sa aktualizovať vzhľad priečinka." folderAppearanceFailedDetail = "Nepodarilo sa aktualizovať vzhľad priečinka: {{message}}" moveFilesFailed = "Nepodarilo sa presunúť súbory." @@ -4113,6 +4170,11 @@ localHint = "Uložené len v tomto prehliadači" shared = "Zdieľané" sharedHint = "Zdieľané s vami cez odkaz" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Najstaršie ako prvé" modifiedDesc = "Najnovšie ako prvé" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "V úložisku nie sú dostupné žiadne súbory. Najprv o open = "Otvoriť" openFile = "Otvoriť súbor" openFiles = "Otvoriť súbory" +selectFile = "Select {{name}}" selectFromStorage = "Vybrať z úložiska" upload = "Nahrať" uploadFile = "Nahrať súbor" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Všetko uložené" +analyzingFields = "Analysing form fields..." extractCsvError = "Nepodarilo sa extrahovať CSV" extractXlsxError = "Nepodarilo sa extrahovať XLSX" +filled = "filled" flattenAfterFilling = "Spľoštiť po vyplnení" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "pov" requiredFieldsError = "Vyplňte všetky povinné polia" rescanFields = "Znovu naskenovať polia" @@ -4795,9 +4863,6 @@ title = "Obrázok na PDF" [imageToPdf] tags = "konverzia,img,jpg,obrázok,fotografia" -[infoBanner] -dismiss = "Zavrieť" - [invite] acceptError = "Nepodarilo sa vytvoriť účet" accountFor = "Vytvára sa účet pre" @@ -4979,6 +5044,34 @@ uploadSuccess = "Nahrávanie úspešné!" uploadSuccessMessage = "Vaše obrázky boli prenesené." validating = "Overuje sa relácia..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Naskenujte na nahratie fotiek. Obrázky sa automaticky konvertujú na PDF." descriptionNoConvert = "Naskenujte na nahratie fotiek z mobilného zariadenia." @@ -4997,8 +5090,13 @@ title = "Nahratie z mobilu" tags = "Multi Tool,Multi operácie,UI,klik drag,front end,beží na klientovi,interaktívne,intraktívne,posunúť" title = "PDF Multi Nástroj" -[navbar] -search = "Hľadať" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Overenie nebolo úspešné. Môžete zavrieť toto okno a skúsiť znova." @@ -5127,6 +5225,7 @@ activeFiles = "Zobrazenie Aktívne súbory zobrazuje všetky PD allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Zavrieť" cropSettings = "Keď sme vybrali súbor, ktorý chceme orezať, môžeme nakonfigurovať nástroj Orez tak, aby sme zvolili oblasť, na ktorú chceme PDF orezať." +dialogLabel = "Onboarding" fileCheckbox = "Kliknutím na niektorý zo súborov ho vyberiete na spracovanie. Môžete vybrať viac súborov pre hromadné operácie." fileReplacement = "Upravený súbor automaticky nahradí pôvodný súbor na pracovnej ploche, čo vám umožní ľahko ho spracovať ďalšími nástrojmi." filesButton = "Tlačidlo Súbory na paneli rýchleho prístupu umožňuje nahrať PDF, na ktorých môžete nástroje používať." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF na PDF/A" tags = "archív,dĺhodobé,štandard,konverzia,uchovanie" title = "PDF na PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "tlač,štandard,konverzia,produkcia,predtlačová príprava,archív" title = "PDF na PDF/X" @@ -6045,6 +6151,7 @@ small = "500 kreditov" xsmall = "100 kreditov" [plan.availablePlans] +currency = "Billing currency" subtitle = "Vyberte si plán, ktorý vám vyhovuje" title = "Dostupné plány" @@ -6624,6 +6731,7 @@ reachedTitle = "Mesačný limit výdavkov bol dosiahnutý" title = "Portál Stripe sa nepodarilo otvoriť" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "z {{allowance}} bezplatných PDF použitých" capSuffix_other = "z {{allowance}} bezplatných PDF použitých" eyebrow = "Skúšobná verzia Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Odošlite dokument alebo upozornenie e-mailom, keď sa spustí pravidlo." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Uložte spracované dokumenty do Nextcloud." @@ -7077,11 +7211,6 @@ title = "Oficiálne SDKs" beta = "Beta" deprecated = "Zastarané" -[portal.docs.search] -empty = "Žiadna zodpovedajúca dokumentácia" -placeholder = "Hľadať v dokumentácii" -results = "{{count}} výsledkov" - [portal.docs.skills] eyebrow = "ZRUČNOSTI" lead = "Zabalené pomenované schopnosti, ktoré váš agent vyvoláva ako jeden nástroj. Každá zručnosť je deterministický reťazec operácií s pripojenými evals." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Tomuto filtru nezodpovedajú žiadne dokumenty." rowActions = "Akcie riadka" sensitiveLabel = "Citlivé" -sensitiveTitle = "Citlivé — vyžaduje sa prístup" [portal.documents.table.columns] action = "Pipeline / Akcia" document = "Dokument" +labels = "Labels" product = "Produkt" status = "Stav" time = "Čas" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Naposledy videné" region = "Región" status = "Stav" +target = "Target" version = "Verzia" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "V tomto zobrazení nastala neočakávaná chyba. Skúste to znova retry = "Skúsiť znova" title = "Na tejto stránke sa niečo pokazilo" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Späť" body = "Stiahnite si desktopovú aplikáciu alebo self-hostujte server." @@ -7380,11 +7560,6 @@ cancel = "Zrušiť" confirm = "Odvolať kľúč" title = "Odvolať API kľúč" -[portal.infrastructure.attestationLabel] -attested = "Atestované" -inScope = "V rozsahu" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtrovať auditné udalosti podľa kategórie" heading = "Auditné záznamy" @@ -7456,11 +7631,6 @@ info = "Info" success = "Úspech" warning = "Upozornenie" -[portal.infrastructure.certLabel] -certified = "Certifikované" -inProgress = "Prebieha" -notStarted = "Nezačaté" - [portal.infrastructure.createKey] cancel = "Zrušiť" createKey = "Vytvoriť kľúč" @@ -7474,238 +7644,10 @@ subtitleCreated = "Skopírujte tento tajný kľúč teraz — znova sa už nezob title = "Vytvoriť API kľúč" titleCreated = "Kľúč vytvorený" -[portal.infrastructure.deployLabel] -live = "Naživo" -queued = "Vo fronte" -rolledBack = "Vrátené späť" -rolling = "Prebieha nasadzovanie" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Nasadil" -environment = "Prostredie" -product = "Produkt" -status = "Stav" -version = "Verzia" -when = "Kedy" - -[portal.infrastructure.deployments.recent] -heading = "Nedávne nasadenia" -subheading = "Najnovšie nasadenia naprieč produktmi a prostrediami." - -[portal.infrastructure.deployments.regionColumns] -instances = "Inštancie" -latency = "Latencia" -load = "Zaťaženie" -p99 = "P99" -region = "Región" -status = "Stav" -throughput = "Priepustnosť" -uptime = "Dostupnosť" -version = "Verzia" - -[portal.infrastructure.deployments.regions] -heading = "Regióny" -subheading = "Živý stav pre každý nasadený región Stirling — latencia, zaťaženie a verzia rollout." - -[portal.infrastructure.deployments.regions.empty] -description = "Nasadené regióny sa tu zobrazia po zriadení vášho pracovného priestoru." -title = "Žiadne nasadené regióny" - [portal.infrastructure.keyLabel] active = "Aktívny" revoked = "Odvolaný" -[portal.infrastructure.modelLabel] -active = "Aktívny" -degraded = "Zhoršený" -disabled = "Zakázaný" - -[portal.infrastructure.models] -heading = "Modely" -msValue = "{{value}} ms" -subheading = "Katalóg modelov a smerovanie, ktoré poháňa spracovanie dokumentov vo vašom pracovnom priestore." - -[portal.infrastructure.models.byom] -description = "Zaregistrujte on-prem alebo self-hosted model a pripnite ho k regiónu na spracovanie viazané na rezidenciu dát." -title = "Prineste si vlastný model" - -[portal.infrastructure.models.catalogue] -heading = "Katalóg" -sub = "Spravované modely dostupné pre váš pracovný priestor, so živou latenciou a nákladmi." -subEnterprise = "Spravované, vlastné a on-prem modely — s dostupným pripnutím podľa regiónu." - -[portal.infrastructure.models.catalogue.empty] -description = "Modely v katalógu vášho pracovného priestoru sa zobrazia tu." -title = "Nie sú dostupné žiadne modely" - -[portal.infrastructure.models.columns] -cost = "Náklady" -latency = "Latencia" -load = "Zaťaženie" -model = "Model" -status = "Stav" -type = "Typ" -version = "Verzia" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/volanie" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktívne modely" -avgLatency = "Priem. latencia" -included = "Zahrnuté" -monthlySpend = "Mesačné náklady na modely" - -[portal.infrastructure.models.routing] -empty = "Nie sú nakonfigurované žiadne pravidlá smerovania." -heading = "Pravidlá smerovania" -sub = "Ktorý model obsluhuje jednotlivé operácie. Predvolené pravidlo sa použije, keď sa nezhoduje žiadne užšie pravidlo." -subLocked = "Smerujte operácie na konkrétne modely — dostupné v platených plánoch." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Inovujte na Pro a ovládajte, ktorý model obsluhuje jednotlivé operácie a typy dokumentov." -title = "Smerovanie modelov je platená funkcia" - -[portal.infrastructure.models.routingColumns] -default = "Predvolené" -docType = "Typ dokumentu" -modelForAria = "Model pre {{operation}}" -operation = "Operácia" -routedTo = "Smerované na" - -[portal.infrastructure.modelTypeLabel] -classification = "Klasifikácia" -extraction = "Extrakcia" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Zhoršené" -down = "Nedostupné" -healthy = "V poriadku" - -[portal.infrastructure.security.access.byok] -description = "Dodajte kľúč z vlastného KMS. Stirling ním šifruje, ale stále môže čítať." -label = "Prineste si vlastný kľúč (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Kľúče nikdy neopustia váš KMS. Stirling uchováva iba ciphertext." -label = "Držte si vlastný kľúč (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling spravuje šifrovacie kľúče. Najjednoduchšie — žiadne operácie s kľúčmi na vašej strane." -label = "Kľúče držané službou Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Politika prístupu k dokumentom" -subheading = "Riadi, kto môže dešifrovať spracované dokumenty v pokoji." - -[portal.infrastructure.security.attestations] -heading = "Potvrdenia zhody" -noReport = "Nie je dostupná žiadna správa" -subheading = "Auditný stav podľa jednotlivých rámcov, so správami dostupnými pre potvrdené kontroly." -viewReport = "Zobraziť správu →" - -[portal.infrastructure.security.compliance] -heading = "Zhoda" -subheading = "Potvrdenia a certifikácie pokrývajúce platformu Stirling." - -[portal.infrastructure.security.empty] -description = "Bezpečnostná konfigurácia vášho pracovného priestoru sa zobrazí tu." -title = "Bezpečnostný stav nie je dostupný" - -[portal.infrastructure.security.hyokBanner] -description = "S HYOK šifrovacie kľúče nikdy neopustia váš KMS. Stirling ukladá a spracúva iba ciphertext, ktorý môžete kedykoľvek odvolať." -title = "Stirling nemôže dešifrovať vaše dokumenty" - -[portal.infrastructure.security.ipAllowlist] -empty = "Nie sú nakonfigurované žiadne rozsahy IP adries — všetky IP adresy sú povolené." -heading = "IP allowlist" -sub = "Prístup k API je obmedzený na tieto rozsahy CIDR." -subLocked = "Obmedzte prístup k API na známe rozsahy IP adries — dostupné v platených plánoch." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Inovujte na Pro a obmedzte prístup k API na konkrétne siete." -title = "IP allowlisting je platená funkcia" - -[portal.infrastructure.security.ipColumns] -added = "Pridané" -addedBy = "Pridal" -cidr = "CIDR" -label = "Štítok" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritmus" -heading = "Správa šifrovacích kľúčov" -keyId = "Identifikátor kľúča" -lastRotated = "Naposledy rotované" -rotateKey = "Rotovať kľúč" -rotationPolicy = "Politika rotácie" -subheading = "Správa kľúčov, ktoré šifrujú dokumenty v pokoji — kto môže dešifrovať a ako sa kľúče rotujú." - -[portal.infrastructure.security.managedBanner] -description = "Správa kľúčov bring-your-own-key (BYOK) a hold-your-own-key (HYOK) je dostupná v Enterprise. Inovujte a dodajte kľúče z vlastného KMS." -title = "Kľúče vo vašom pláne spravuje Stirling" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Ázia a Tichomorie" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · dátová hranica GDPR" -label = "Európska únia" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Spojené štáty" - -[portal.infrastructure.security.residencyHeader] -heading = "Rezidencia dát" -subheading = "Kde sa dokumenty ukladajú a spracúvajú." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} použité" - -[portal.infrastructure.storage.empty] -description = "Pripojené úložisko a použitie sa zobrazia tu." -title = "Nie je nakonfigurované žiadne úložisko" - -[portal.infrastructure.storage.lifecycle] -active = "Aktívne" -activeRange = "0–{{value}} d" -archived = "Archivované" -coldStorage = "studené úložisko" -deleted = "Odstránené" -never = "nikdy" -purged = "vyčistené" - -[portal.infrastructure.storage.providers] -connect = "Pripojiť" -connected = "Pripojené" -heading = "Pripojení poskytovatelia" -subheading = "Kam sa zapisujú spracované artefakty." - -[portal.infrastructure.storage.retention] -heading = "Uchovávanie" -subheading = "Ako dlho sa artefakty uchovávajú pred odstránením v životnom cykle." -windowLabel = "Predvolené okno uchovávania" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} deň" -days_other = "{{count}} dní" -never = "Nikdy neodstraňovať" - -[portal.infrastructure.storage.totalUsage] -heading = "Celkové použitie" -progressLabel = "Použité úložisko" -subheading = "Úložisko spotrebované naprieč všetkými pripojenými poskytovateľmi." - [portal.infrastructure.tabs] apiKeys = "API kľúče" audit = "Auditné záznamy" @@ -7715,11 +7657,9 @@ security = "Bezpečnosť" storage = "Úložisko" [portal.integrations] -addAnother = "Pridať ďalšiu" availableHeading = "Dostupné" comingSoonHeading = "Čoskoro" connect = "Pripojiť" -connectedHeading = "Pripojené" connectionCount_one = "{{count}} pripojenie" connectionCount_other = "{{count}} pripojení" customApi = "Vlastné API" @@ -7737,6 +7677,10 @@ security = "Zabezpečenie" signing = "Podpisovanie" storage = "Úložisko" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Pripojené" @@ -7767,7 +7711,6 @@ integrations = "Integrácie" pipelines = "Pipeline" policies = "Politiky" procurement = "Obstarávanie" -settings = "Nastavenia" sources = "Zdroje" usage = "Používanie a fakturácia" users = "Používatelia" @@ -7784,7 +7727,7 @@ title = "Pipeline" newPipeline = "Nový pipeline" [portal.pipelines.builder] -addStep = "Pridať nástroj" +activate = "Activate" back = "Späť na pipeline" cannotFollow = "Nie je možné prevziať {{produced}}" chooseAccount = "Vyberte účet" @@ -7792,28 +7735,38 @@ chooseDestination = "Vyberte cieľ" chooseOperation = "Vyberte, čo tento krok vykoná" chooseSource = "Vyberte zdroj" discard = "Zahodiť zmeny" -enabled = "Povolené" inputs = "Vstup" inputSource = "Vstupný zdroj" inputTrigger = "Spúšťač" keepEditing = "Pokračovať v úpravách" +moreActions = "More actions" needsConfiguring = "Vyžaduje nastavenie" -needsUpload = "Vyžaduje nahraný súbor" -noSources = "Zatiaľ nie sú pripojené žiadne zdroje. Vytvorte jeden, aby ste ho mohli použiť ako vstup." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Vašemu vyhľadávaniu nezodpovedajú žiadne nástroje." -pipelineSettings = "Nastavenia pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Hľadať nástroje" -selectToolBody = "Pridajte nástroj na vytvorenie pipeline." -selectToolTitle = "Zatiaľ žiadne nástroje" sendToSystem = "Odoslať do iného systému" stepsIncompatible = "Tieto kroky nemožno spustiť na tom, čo vytvára ich predchádzajúci krok: {{tools}}." stepsNeedSetup = "Tieto kroky je pred uložením ešte potrebné nastaviť: {{tools}}." -toolSettings = "Nastavenia nástroja" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Nerozpoznaná operácia, ponechaná bez zmeny." unsavedBody = "Máte neuložené zmeny. Uložte ich pred odchodom alebo ich zahodiť?" unsavedTitle = "Neuložené zmeny" -uploadUnsupported = "Nahrané súbory ešte nie sú v pipeline podporované, takže tieto kroky sa nedajú uložiť: {{tools}}." usesDefaults = "Spúšťa sa s predvolenými nastaveniami" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kombinuje každý súbor z predchádzajúceho kroku" @@ -7825,31 +7778,36 @@ undeclared-operation = "Nie je možné skontrolovať, čo tento krok prijíma" [portal.pipelines.composer] addTool = "Pridať nástroj" -cancel = "Zrušiť" -chainEmpty = "Pridajte nástroj a začnite vytvárať pipeline." create = "Vytvoriť pipeline" +createPaused = "Create paused" editingUnsupported = "Zobrazenie týchto parametrov nástroja na úpravu zatiaľ nie je podporované." -moveDown = "Presunúť nadol" -moveUp = "Presunúť nahor" +editSource = "Edit source" name = "Názov" namePlaceholder = "napr. Prechod redakciou" noToolSettings = "Tento nástroj nemá žiadne konfigurovateľné nastavenia." -operations_one = "Operácia ({{count}})" -operations_other = "Operácie ({{count}})" output = "Výstup" -removeStep = "Odstrániť operáciu" save = "Uložiť zmeny" scheduleEvery = "Spúšťať každých" -sources = "Zdroje" -sourcesLoading = "Načítavajú sa zdroje..." trigger = "Trigger" triggerManual = "Iba manuálne" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dní" hours = "hodín" minutes = "minút" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Odstrániť \"{{name}}\"? Túto akciu nie je možné vrátiť späť." cancel = "Zrušiť" @@ -7867,6 +7825,37 @@ connectSource = "Pripojiť zdroj" description = "Vytvorte svoj prvý pipeline: vyberte zdroje, nad ktorými beží, spojte operácie do reťazca a vyberte, kam pôjde výstup." title = "Zatiaľ žiadne pipeline" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktívne" paused = "Pozastavené" @@ -7879,7 +7868,6 @@ completed_one = "Spustenie dokončené." completed_other = "Všetkých {{count}} spustení dokončených." empty = "Nie je čo spustiť: zdroje nemali žiadne dokumenty na spracovanie." failed = "Spustenie zlyhalo: {{error}}" -historyCleared = "História bola vymazaná. Ďalšie spustenie znova spracuje všetko, čo sa aktuálne nachádza v zdrojoch." inFlight = "Nie je nič nové na spustenie: dokumenty sa stále spracúvajú z predchádzajúceho spustenia." parked_one = "Nie je čo spustiť: {{count}} dokument predtým zlyhal a je pozastavený. Opravte príčinu a potom vymažte históriu, aby ste ho skúsili znova." parked_other = "Nie je čo spustiť: {{count}} dokumentov predtým zlyhalo a sú pozastavené. Opravte príčinu a potom vymažte históriu, aby ste ich skúsili znova." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Zdroje" status = "Stav" steps = "Kroky" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Sledovanie priečinka" @@ -8209,6 +8198,24 @@ label = "Pripojiť k Jira issue" description = "Odošle spracovaný dokument ako prílohu." label = "Odoslať dokument emailom (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Zapíše spracovaný dokument do priečinka." label = "Nahrať do Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Maximum dosiahnuté" subtitle = "Vaša bezplatná skúšobná verzia trvá do {{date}}. Karta nie je potrebná." title = "Enterprise skúšobná verzia" -[portal.search] -ariaLabel = "Hľadať" -placeholder = "Hľadať v Stirling — endpointy, pipeline, dokumenty…" - -[portal.search.empty] -noActionsDescription = "Rýchle akcie sa tu zobrazia, keď budú dostupné." -noActionsTitle = "Žiadne rýchle akcie" -noMatches = "Žiadne zhody pre „{{query}}“" -noMatchesDescription = "Skúste iné kľúčové slovo alebo prejdite katalóg." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Zrušiť" confirm = "Odstrániť" title = "Odstrániť zdroj?" -[portal.sources.empty] -description = "Pripojte priečinok (a čoskoro aj cloudové úložisko), aby mali vaše politiky odkiaľ získavať dokumenty." -title = "Zatiaľ nie sú pripojené žiadne zdroje" - [portal.sources.kpi] inUse = "Používa sa" total = "Pripojenia" @@ -8785,6 +8778,7 @@ unused = "Nepoužité" documents = "Dokumenty" source = "Zdroj" status = "Stav" +type = "Type" usedBy = "Politiky" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Zásady a súkromie" [settings.preferences] title = "Predvoľby" -[settings.search] -placeholder = "Hľadať na stránkach nastavení..." - [settings.security] description = "Aktualizujte svoje heslo, aby bol váš účet v bezpečí." title = "Zabezpečenie" @@ -10128,6 +10119,7 @@ last = "Posledná strana" maintainRatio = "Prepnúť zachovanie pomeru strán" next = "Ďalšia strana" noSavedSigs = "Nenašli sa žiadne uložené podpisy" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Osobné podpisy" previous = "Predchádzajúca strana" redo = "Znova" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Odstránenie pozadia zlyhalo" hint = "Nahrajte obrázok vášho podpisu vo formáte PNG alebo JPG" label = "Nahrať obrázok podpisu" placeholder = "Vyberte súbor obrázka" +previewAlt = "Current image signature" processing = "Spracovanie obrázka..." removeBackground = "Odstrániť biele pozadie (spriehľadniť)" @@ -10179,6 +10172,17 @@ saved = "Vyberte uložený podpis vyššie, potom kliknite kdekoľvek do PDF na text = "Po zadaní svojho mena vyššie kliknite kdekoľvek do PDF, aby ste umiestnili svoj podpis." title = "Ako pridať podpis" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Presúvať podpis" pause = "Pozastaviť umiestňovanie" @@ -10625,6 +10629,23 @@ title = "Nahrať na server" updateButton = "Aktualizovať na serveri" uploadButton = "Nahrať na server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Prejsť späť" pdfTools = "Nástroje PDF" placeholder = "Vyberte nástroj, aby ste začali" premiumFeature = "Prémiová funkcia:" -searchTools = "Hľadať nástroje" toolsHeader = "Nástroje" viewAllTools = "Zobraziť všetky nástroje" @@ -10822,6 +10842,9 @@ removal = "Odstraňovanie" signing = "Podpisovanie" verification = "Overovanie" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nenašli sa žiadne nástroje" noTools = "Nie sú dostupné žiadne nástroje" @@ -10979,6 +11002,13 @@ approver = "Schvaľuje politiku" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Zrušiť pozvanie pre {{email}}? S aktuálnym odkazom sa nebudú môcť pripojiť." cancelInviteTitle = "Zrušiť pozvanie" @@ -10998,10 +11028,8 @@ title = "Zatiaľ žiadni členovia" addToTeam = "Pridať do tímu" guestCount = "{{count}} hosť" guests = "Hostia" -guestsDesc = "Externí spolupracovníci, obmedzení na to, čo ste zdieľali. Iba Editor." ledBy = "vedie {{owner}}" org = "Organizácia" -orgDesc = "Vlastníci s celofiremnou autoritou a schvaľovaním politík" owners = "{{count}} vlastník" team = "Tím {{name}}" teamMeta = "{{count}} ľudí" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Pozvanie od {{who}}" cancel = "Zrušiť" -count = "{{count}} čaká" -desc = "Pozvaní ľudia, ktorí sa ešte nepripojili. Zaberajú miesto, kým pozvanie neprijmú." expiresInDays_one = "Platnosť vyprší o {{count}} deň" expiresInDays_other = "Platnosť vyprší o {{count}} dní" expiresToday = "Platnosť vyprší dnes" title = "Čakajúce pozvania" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Pri pripájaní k backendu sa niečo pokazilo alebo nemáte prístup. Skúste to znova." title = "Nepodarilo sa načítať členov" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Hľadať prílohy" title = "Prílohy" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Názov záložky" bookmarkTitleRequired = "Názov záložky je povinný" closeSidebar = "Zavrieť bočný panel záložiek" collapseAll = "Zbaliť všetky záložky" +empty = "No bookmarks in this document" expandAll = "Rozbaliť všetky záložky" searchPlaceholder = "Hľadať záložky" +title = "Bookmarks" [viewer.comments] addComment = "Pridať komentár" @@ -11264,10 +11297,12 @@ locateAnnotation = "Vyhľadať v dokumente" moreActions = "Ďalšie akcie" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Strana {{page}}" placingHint = "Kliknite na stránku a umiestnite… (zrušiť)" removeCommentOnly = "Odstrániť iba komentár" saveReply = "Uložiť odpoveď" +searchPlaceholder = "Search comments" title = "Komentáre" typeComment = "Komentár" typeInsertText = "Vložiť text" @@ -11290,6 +11325,7 @@ unsavedDesc = "Máte neuložené zmeny" closeSidebar = "Zavrieť bočný panel vrstiev" hideAll = "Skryť všetky vrstvy" showAll = "Zobraziť všetky vrstvy" +title = "Layers" [viewer.link] delete = "Odstrániť odkaz" @@ -11646,6 +11682,7 @@ exitRedaction = "Ukončiť režim začiernenia" exportAll = "Exportovať PDF" exportSelected = "Exportovať vybrané strany" formFill = "Vyplniť formulár" +hideToolbar = "Hide toolbar" multiTool = "Multinástroj" panMode = "Režim posunu" print = "Tlačiť PDF" @@ -11666,6 +11703,7 @@ selectAll = "Vybrať všetko" selectByNumber = "Vybrať podľa čísel strán" selectLanguage = "Vybrať jazyk" share = "Zdieľať" +showToolbar = "Show toolbar" toggleAnnotations = "Prepnúť viditeľnosť anotácií" toggleAttachments = "Prepnúť prílohy" toggleBookmarks = "Prepnúť záložky" diff --git a/frontend/editor/public/locales/sl-SI/translation.toml b/frontend/editor/public/locales/sl-SI/translation.toml index 6c0e56dca9..dda187e356 100644 --- a/frontend/editor/public/locales/sl-SI/translation.toml +++ b/frontend/editor/public/locales/sl-SI/translation.toml @@ -1,7 +1,6 @@ alphabet = "Abeceda" apply = "Uporabi" applyAndContinue = "Shrani in zapusti" -areYouSure = "Ali ste prepričani, da želite oditi?" back = "Nazaj" black = "črna" blue = "modra" @@ -106,6 +105,7 @@ undoSuccess = "Operacija uspešno razveljavljena" unknown = "Neznano" unpin = "Odpni datoteko (zamenjaj po zagonu orodja)" unsavedChanges = "Imate neshranjene spremembe v vašem PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Neshranjene spremembe" unsupported = "Nepodprto" username = "Uporabniško ime" @@ -1879,6 +1879,9 @@ width = "Širina" [app] description = "Brezplačna alternativa Adobe Acrobat (10M+ prenosov)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Pretvori v PDF/A-3b" convertToPdfA3bDescription = "Ustvari arhivski PDF z vdelanimi prilogami" @@ -2933,12 +2936,16 @@ text = "Da bodo ta dovoljenja nespremenljiva, uporabite orodje Dodaj geslo za na [chat.actions] copy = "Kopiraj sporočilo" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Možnosti agenta Stirling" +assistant = "AI Assistant" clearChat = "Počisti klepet" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Ta operacija bo uporabila vaše kredite v oblaku" tooltip = "Izberi barvo z zaslona" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Izberite barvo" [common] +actions = "Actions" back = "Nazaj" cancel = "Prekliči" close = "Zapri" +codeSample = "Code sample" collapse = "Strni" confirm = "Potrdi" continue = "Nadaljuj" copied = "Kopirano!" copy = "Kopiraj" done = "Končano" +enterValue = "Enter value" error = "Napaka" expand = "Razširi" loading = "Nalaganje..." next = "Naslednje" +open = "Open" preview = "Predogled" previous = "Prejšnje" refresh = "Osveži" remaining = "Preostalo" retry = "Poskusi znova" save = "Shrani" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Počisti izbrano" @@ -3185,6 +3199,7 @@ title = "Način stiskanja" [compress.settings] desiredSize = "Želena velikost datoteke" desiredSizePlaceholder = "Vnesite velikost" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Stiskanje je enostaven način za zmanjšanje velikosti datoteke. Izberite Velikost datoteke za vnos ciljne velikosti in mi bomo prilagodili kakovost namesto vas. Izberite Kakovost, če želite ročno nastaviti moč stiskanja." @@ -3387,6 +3402,24 @@ pdfOptions = "Možnosti PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Najprej izberite izvorni format" settings = "Nastavitve" single = "Posamezna" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Išči..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Uredi" @@ -3824,11 +3861,13 @@ shareSelected = "Daj v skupno rabo izbrano" sharing = "Skupna raba" showAll = "Prikaži vse" showHistory = "Prikaži zgodovino" +sortBy = "Sort files" sortByDate = "Razvrsti po datumu" sortByName = "Razvrsti po imenu" sortBySize = "Razvrsti po velikosti" storage = "Shramba" storageState = "Shramba" +storageUsed = "Storage used" synced = "Sinhronizirano" title = "Naloži datoteke PDF" toolChain = "Uporabljena orodja" @@ -3874,8 +3913,12 @@ addFiles = "Dodaj datoteke" addingFiles = "Dodajanje datotek…" collapse = "Strni stransko vrstico" customizeGroups = "Prilagodi skupine" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Odprite datoteke, da začnete" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Razširi stransko vrstico" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive ni konfiguriran" @@ -3888,15 +3931,17 @@ openFromComputer = "Odpri iz računalnika" openSettings = "Odpri nastavitve" other = "Drugo" recent = "Nedavno" -search = "Išči" -searchPlaceholder = "Išči datoteke..." viewAll = "Prikaži vseh {{count}} datotek" [fileSidebar.fileItem] closeViewer = "Zapri pregledovalnik" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Odpri v pregledovalniku" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Ponastavi na privzeto" subtitle = "Združite svoje datoteke v nadrejene kategorije. V kategorijo dodajte obstoječe ali nove oznake, jo preimenujte ali ustvarite svojo. Datoteke, ki niso v nobeni od vaših kategorij, se prikažejo pod »Drugo«." title = "Kategorije stranske vrstice" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Dodaj v delovni prostor" addToWorkspaceCount = "Dodaj {{count}} v delovni prostor" @@ -3918,7 +3971,6 @@ backToMyFiles = "Nazaj v Moje datoteke" breadcrumbs = "Pot mape" cancel = "Prekliči" classification = "Klasifikacija" -clearSearch = "Počisti iskanje" clearSelection = "Počisti izbiro" closeDetails = "Zapri podrobnosti" create = "Ustvari" @@ -3950,6 +4002,7 @@ downloadAll = "Prenesi vse" downloadVersion = "Prenesi to različico" dropOverlay = "Spustite datoteke, da jih naložite" dropOverlaySub = "Datoteke se najprej shranijo v Lokalno. Uporabite 'Premakni v' ali 'Shrani v oblak' za organizacijo v mape." +duplicate = "Duplicate" file = "Datoteka" fileInfo = "File info" fileMenu = "Dejanja z datoteko" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Spremeni velikost drevesa map (smernimi tipkami, Shift za ve save = "Shrani" saveToServer = "Shrani na strežnik" saveToServerDisabledHint = "Shranjevanje na strežnik ni omogočeno na tem strežniku. Prosite skrbnika, naj to omogoči." -search = "Iskanje" -searchPlaceholder = "Iščite po tej mapi in podmapah" selectAll = "Izberi vse" selectAllHint = "Kliknite za izbiro vseh. Namig: držite Ctrl (ali Cmd) za dodajanje datotek posamično, Shift za izbiro obsega." selectedCount = "Izbranih {{count}}" @@ -4044,6 +4095,10 @@ title = "Še ni oblačnih datotek" hint = "Datoteke, shranjene brez nalaganja, ostanejo tukaj. Spustite datoteko za dodajanje." title = "Ni lokalnih datotek" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Datoteke, ki jih odprete ali uredite, se bodo pojavile tukaj." title = "Še nič ni spremenjeno" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Mape ni bilo mogoče izbrisati." deleteFolderFailedDetail = "Mape ni bilo mogoče izbrisati: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Ni bilo mogoče posodobiti videza mape." folderAppearanceFailedDetail = "Ni bilo mogoče posodobiti videza mape: {{message}}" moveFilesFailed = "Ni bilo mogoče premakniti datotek." @@ -4113,6 +4170,11 @@ localHint = "Shranjeno samo v tem brskalniku" shared = "V skupni rabi" sharedHint = "V skupni rabi z vami prek povezave" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Najstarejše najprej" modifiedDesc = "Najnovejše najprej" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "V shrambi ni datotek. Najprej odprite nekaj datotek." open = "Odpri" openFile = "Odpri datoteko" openFiles = "Odpri datoteke" +selectFile = "Select {{name}}" selectFromStorage = "Izberi iz shrambe" upload = "Naloži" uploadFile = "Naloži datoteko" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Vse shranjeno" +analyzingFields = "Analysing form fields..." extractCsvError = "Izvlečenje CSV ni uspelo" extractXlsxError = "Izvlečenje XLSX ni uspelo" +filled = "filled" flattenAfterFilling = "Splošči po izpolnjevanju" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obv." requiredFieldsError = "Izpolnite vsa obvezna polja" rescanFields = "Znova preglej polja" @@ -4795,9 +4863,6 @@ title = "Slika v PDF" [imageToPdf] tags = "pretvorba,img,jpg,slika,fotografija" -[infoBanner] -dismiss = "Opusti" - [invite] acceptError = "Računa ni bilo mogoče ustvariti" accountFor = "Ustvarjanje računa za" @@ -4979,6 +5044,34 @@ uploadSuccess = "Nalaganje uspešno!" uploadSuccessMessage = "Vaše slike so bile prenesene." validating = "Preverjanje seje..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Skenirajte za nalaganje fotografij. Slike se samodejno pretvorijo v PDF." descriptionNoConvert = "Skenirajte za nalaganje fotografij z vaše mobilne naprave." @@ -4997,8 +5090,13 @@ title = "Naloži iz mobilne naprave" tags = "Več orodij, več operacij, uporabniški vmesnik, klik povleci, sprednji del, odjemalska stran, interaktivno, nepremagljivo, premakni, izbriši, preseli, razdeli" title = "PDF večnamensko orodje" -[navbar] -search = "Išči" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Overjanje ni bilo uspešno. Okno lahko zaprete in poskusite znova." @@ -5127,6 +5225,7 @@ activeFiles = "Pogled Aktivne datoteke prikaže vse PDF-je, ki allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Zapri" cropSettings = "Zdaj ko smo izbrali datoteko, ki jo želimo obrezati, lahko nastavimo orodje Obreži in izberemo območje, na katerega želimo obrezati PDF." +dialogLabel = "Onboarding" fileCheckbox = "S klikom na eno izmed datotek jo izberete za obdelavo. Izberete lahko več datotek za paketne operacije." fileReplacement = "Spremenjena datoteka bo samodejno zamenjala izvirno datoteko na Delovni mizi, kar omogoča, da jo zlahka poženete skozi dodatna orodja." filesButton = "Gumb Datoteke v vrstici hitrega dostopa omogoča nalaganje PDF-jev, na katerih želite uporabljati orodja." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF v PDF/A" tags = "arhiviranje,dolgoročno,standard,pretvorba,shranjevanje,hramba" title = "PDF v PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "tiskanje,standard,pretvorba,produkcija,predtisk,arhiv" title = "PDF v PDF/X" @@ -6045,6 +6151,7 @@ small = "500 kreditov" xsmall = "100 kreditov" [plan.availablePlans] +currency = "Billing currency" subtitle = "Izberite paket, ki ustreza vašim potrebam" title = "Razpoložljivi paketi" @@ -6624,6 +6731,7 @@ reachedTitle = "Mesečna omejitev porabe dosežena" title = "Portala Stripe ni bilo mogoče odpreti" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "od {{allowance}} brezplačnih PDF-jev porabljenih" capSuffix_other = "od {{allowance}} brezplačnih PDF-jev porabljenih" eyebrow = "Preizkus Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Pošljite dokument ali obvestilo po Email, ko se pravilo izvede." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Shranjujte obdelane dokumente v Nextcloud." @@ -7077,11 +7211,6 @@ title = "Uradni SDK-ji" beta = "Beta" deprecated = "Zastarelo" -[portal.docs.search] -empty = "Ni ujemajoče se dokumentacije" -placeholder = "Išči dokumentacijo" -results = "{{count}} rezultatov" - [portal.docs.skills] eyebrow = "VEŠČINE" lead = "Vključene, poimenovane zmožnosti, ki jih vaš agent prikliče kot eno orodje. Vsaka veščina je deterministična veriga operacij s priloženimi evalvacijami." @@ -7180,11 +7309,11 @@ editorAction = "Urejevalnik" empty = "Noben dokument se ne ujema s tem filtrom." rowActions = "Dejanja vrstice" sensitiveLabel = "Občutljivo" -sensitiveTitle = "Občutljivo — potreben je dostop" [portal.documents.table.columns] action = "Pipeline / Dejanje" document = "Dokument" +labels = "Labels" product = "Izdelek" status = "Stanje" time = "Čas" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Nazadnje videno" region = "Regija" status = "Stanje" +target = "Target" version = "Različica" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "V tem pogledu je prišlo do nepričakovane napake. Poskusite znov retry = "Poskusite znova" title = "Na tej strani je šlo nekaj narobe" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Nazaj" body = "Prenesite namizno aplikacijo ali samogostujte strežnik." @@ -7380,11 +7560,6 @@ cancel = "Prekliči" confirm = "Prekliči ključ" title = "Prekliči API ključ" -[portal.infrastructure.attestationLabel] -attested = "Potrjeno" -inScope = "V obsegu" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtriraj revizijske dogodke po kategoriji" heading = "Revizijski dnevniki" @@ -7456,11 +7631,6 @@ info = "Info" success = "Uspeh" warning = "Opozorilo" -[portal.infrastructure.certLabel] -certified = "Certificirano" -inProgress = "V teku" -notStarted = "Ni začeto" - [portal.infrastructure.createKey] cancel = "Prekliči" createKey = "Ustvari ključ" @@ -7474,238 +7644,10 @@ subtitleCreated = "Kopirajte to skrivnost zdaj — ne bo več prikazana." title = "Ustvari API ključ" titleCreated = "Ključ ustvarjen" -[portal.infrastructure.deployLabel] -live = "V živo" -queued = "V čakalni vrsti" -rolledBack = "Povrnjeno" -rolling = "Uvajanje" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Namestil" -environment = "Okolje" -product = "Izdelek" -status = "Stanje" -version = "Različica" -when = "Kdaj" - -[portal.infrastructure.deployments.recent] -heading = "Nedavne namestitve" -subheading = "Najnovejše uvedbe po izdelkih in okoljih." - -[portal.infrastructure.deployments.regionColumns] -instances = "Primerki" -latency = "Zakasnitev" -load = "Obremenitev" -p99 = "P99" -region = "Regija" -status = "Stanje" -throughput = "Prepustnost" -uptime = "Čas delovanja" -version = "Različica" - -[portal.infrastructure.deployments.regions] -heading = "Regije" -subheading = "Stanje v živo za vsako nameščeno regijo Stirling — zakasnitev, obremenitev in različica uvedbe." - -[portal.infrastructure.deployments.regions.empty] -description = "Nameščene regije se prikažejo tukaj, ko je vaš delovni prostor pripravljen." -title = "Ni nameščenih regij" - [portal.infrastructure.keyLabel] active = "Aktivno" revoked = "Preklicano" -[portal.infrastructure.modelLabel] -active = "Aktivno" -degraded = "Oslabljeno" -disabled = "Onemogočeno" - -[portal.infrastructure.models] -heading = "Modeli" -msValue = "{{value}} ms" -subheading = "Katalog modelov in usmerjanje, ki poganja obdelavo dokumentov v vašem delovnem prostoru." - -[portal.infrastructure.models.byom] -description = "Registrirajte on-prem ali samogostovan model in ga pripnite na regijo za obdelavo, vezano na rezidentnost podatkov." -title = "Prinesite svoj model" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Upravljani modeli, ki so na voljo vašemu delovnemu prostoru, z zakasnitvijo in stroški v živo." -subEnterprise = "Upravljani, lastni in on-prem modeli — z možnostjo pripenjanja po regijah." - -[portal.infrastructure.models.catalogue.empty] -description = "Modeli v katalogu vašega delovnega prostora se prikažejo tukaj." -title = "Ni razpoložljivih modelov" - -[portal.infrastructure.models.columns] -cost = "Strošek" -latency = "Zakasnitev" -load = "Obremenitev" -model = "Model" -status = "Stanje" -type = "Vrsta" -version = "Različica" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/klic" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktivni modeli" -avgLatency = "Povp. zakasnitev" -included = "Vključeno" -monthlySpend = "Mesečna poraba za modele" - -[portal.infrastructure.models.routing] -empty = "Ni konfiguriranih pravil usmerjanja." -heading = "Pravila usmerjanja" -sub = "Kateri model obravnava posamezno operacijo. Privzeto velja, kadar se ne ujema nobeno ožje pravilo." -subLocked = "Usmerite operacije na določene modele — na voljo v plačljivih paketih." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Nadgradite na Pro, da nadzirate, kateri model obravnava posamezno operacijo in vrsto dokumenta." -title = "Usmerjanje modelov je plačljiva funkcija" - -[portal.infrastructure.models.routingColumns] -default = "Privzeto" -docType = "Vrsta dokumenta" -modelForAria = "Model za {{operation}}" -operation = "Operacija" -routedTo = "Usmerjeno na" - -[portal.infrastructure.modelTypeLabel] -classification = "Klasifikacija" -extraction = "Ekstrakcija" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Oslabljeno" -down = "Nedelujoče" -healthy = "Zdravo" - -[portal.infrastructure.security.access.byok] -description = "Zagotovite ključ iz svojega KMS. Stirling z njim šifrira, vendar lahko še vedno bere." -label = "Prinesite svoj ključ (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Ključi nikoli ne zapustijo vašega KMS. Stirling hrani samo šifrirano besedilo." -label = "Obdržite svoj ključ (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling upravlja šifrirne ključe. Najpreprosteje — brez operacij s ključi na vaši strani." -label = "Ključi pri Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Pravilnik dostopa do dokumentov" -subheading = "Nadzira, kdo lahko dešifrira obdelane dokumente v mirovanju." - -[portal.infrastructure.security.attestations] -heading = "Potrdila o skladnosti" -noReport = "Poročilo ni na voljo" -subheading = "Revizijska drža po posameznih okvirjih, s poročili, ki so na voljo za potrjene kontrole." -viewReport = "Ogled poročila →" - -[portal.infrastructure.security.compliance] -heading = "Skladnost" -subheading = "Potrdila in certifikati, ki pokrivajo platformo Stirling." - -[portal.infrastructure.security.empty] -description = "Varnostna konfiguracija vašega delovnega prostora se bo prikazala tukaj." -title = "Varnostna drža ni na voljo" - -[portal.infrastructure.security.hyokBanner] -description = "Pri HYOK šifrirni ključi nikoli ne zapustijo vašega KMS. Stirling shranjuje in obdeluje samo šifrirano besedilo, ki ga lahko kadar koli prekličete." -title = "Stirling ne more dešifrirati vaših dokumentov" - -[portal.infrastructure.security.ipAllowlist] -empty = "Ni konfiguriranih razponov IP — vsi IP-ji so dovoljeni." -heading = "Seznam dovoljenih IP" -sub = "API dostop je omejen na te razpone CIDR." -subLocked = "Omejite API dostop na znane razpone IP — na voljo v plačljivih paketih." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Nadgradite na Pro, da omejite API dostop na določena omrežja." -title = "Seznami dovoljenih IP so plačljiva funkcija" - -[portal.infrastructure.security.ipColumns] -added = "Dodano" -addedBy = "Dodal" -cidr = "CIDR" -label = "Oznaka" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritem" -heading = "Upravljanje šifrirnih ključev" -keyId = "Identifikator ključa" -lastRotated = "Nazadnje zavrteno" -rotateKey = "Zavrti ključ" -rotationPolicy = "Pravilnik vrtenja" -subheading = "Skrbništvo nad ključi, ki šifrirajo dokumente v mirovanju — kdo lahko dešifrira in kako se ključi vrtijo." - -[portal.infrastructure.security.managedBanner] -description = "Skrbništvo bring-your-own-key (BYOK) in hold-your-own-key (HYOK) je na voljo v Enterprise. Nadgradite, da zagotovite ključe iz svojega KMS." -title = "Ključe v vašem paketu upravlja Stirling" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Azija-Pacifik" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR meja podatkov" -label = "Evropska unija" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Združene države" - -[portal.infrastructure.security.residencyHeader] -heading = "Rezidentnost podatkov" -subheading = "Kje so dokumenti shranjeni in obdelani." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} uporabljeno" - -[portal.infrastructure.storage.empty] -description = "Povezana shramba in uporaba se prikažeta tukaj." -title = "Shramba ni konfigurirana" - -[portal.infrastructure.storage.lifecycle] -active = "Aktivno" -activeRange = "0–{{value}}d" -archived = "Arhivirano" -coldStorage = "hladna shramba" -deleted = "Izbrisano" -never = "nikoli" -purged = "trajno izbrisano" - -[portal.infrastructure.storage.providers] -connect = "Poveži" -connected = "Povezano" -heading = "Povezani ponudniki" -subheading = "Kam se zapisujejo obdelani artefakti." - -[portal.infrastructure.storage.retention] -heading = "Hramba" -subheading = "Kako dolgo se artefakti hranijo pred brisanjem v življenjskem ciklu." -windowLabel = "Privzeto obdobje hrambe" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dan" -days_other = "{{count}} dni" -never = "Nikoli ne izbriši" - -[portal.infrastructure.storage.totalUsage] -heading = "Skupna uporaba" -progressLabel = "Uporabljena shramba" -subheading = "Shramba, porabljena pri vseh povezanih ponudnikih." - [portal.infrastructure.tabs] apiKeys = "API ključi" audit = "Revizijski dnevniki" @@ -7715,11 +7657,9 @@ security = "Varnost" storage = "Shramba" [portal.integrations] -addAnother = "Dodaj še eno" availableHeading = "Na voljo" comingSoonHeading = "Kmalu na voljo" connect = "Poveži" -connectedHeading = "Povezano" connectionCount_one = "{{count}} povezava" connectionCount_other = "{{count}} povezav" customApi = "API po meri" @@ -7737,6 +7677,10 @@ security = "Varnost" signing = "Podpisovanje" storage = "Shramba" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Povezano" @@ -7767,7 +7711,6 @@ integrations = "Integracije" pipelines = "Pipelines" policies = "Pravilniki" procurement = "Nabava" -settings = "Nastavitve" sources = "Viri" usage = "Uporaba in obračunavanje" users = "Uporabniki" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Nov pipeline" [portal.pipelines.builder] -addStep = "Dodaj orodje" +activate = "Activate" back = "Nazaj na pipelines" cannotFollow = "Ne more sprejeti {{produced}}" chooseAccount = "Izberite račun" @@ -7792,28 +7735,38 @@ chooseDestination = "Izberite cilj" chooseOperation = "Izberite, kaj naredi ta korak" chooseSource = "Izberite vir" discard = "Zavrzi spremembe" -enabled = "Omogočeno" inputs = "Vhod" inputSource = "Vhodni vir" inputTrigger = "Sprožilec" keepEditing = "Nadaljuj urejanje" +moreActions = "More actions" needsConfiguring = "Potrebna je nastavitev" -needsUpload = "Potrebuje naloženo datoteko" -noSources = "Viri še niso povezani. Ustvarite enega, da ga uporabite kot vhod." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Nobeno orodje se ne ujema z vašim iskanjem." -pipelineSettings = "Nastavitve pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Išči orodja" -selectToolBody = "Dodajte orodje za gradnjo pipeline." -selectToolTitle = "Orodij še ni" sendToSystem = "Pošlji v drug sistem" stepsIncompatible = "Ti koraki se ne morejo zagnati na tem, kar proizvede njihov prejšnji korak: {{tools}}." stepsNeedSetup = "Te korake je treba pred shranjevanjem še nastaviti: {{tools}}." -toolSettings = "Nastavitve orodja" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Neprepoznana operacija, ohranjena takšna, kot je." unsavedBody = "Imate neshranjene spremembe. Jih želite shraniti pred odhodom ali jih zavreči?" unsavedTitle = "Neshranjene spremembe" -uploadUnsupported = "Naložene datoteke v pipelines še niso podprte, zato teh korakov ni mogoče shraniti: {{tools}}." usesDefaults = "Zažene se s privzetimi nastavitvami" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Združi vsako datoteko iz prejšnjega koraka" @@ -7825,31 +7778,36 @@ undeclared-operation = "Ni mogoče preveriti, kaj ta korak sprejme" [portal.pipelines.composer] addTool = "Dodaj orodje" -cancel = "Prekliči" -chainEmpty = "Dodajte orodje, da začnete graditi svoj pipeline." create = "Ustvari pipeline" +createPaused = "Create paused" editingUnsupported = "Prikaz teh parametrov orodja za urejanje še ni podprt." -moveDown = "Premakni dol" -moveUp = "Premakni gor" +editSource = "Edit source" name = "Ime" namePlaceholder = "npr. Pregled redakcij" noToolSettings = "To orodje nima nastavljivih možnosti." -operations_one = "Operacija ({{count}})" -operations_other = "Operacije ({{count}})" output = "Izhod" -removeStep = "Odstrani operacijo" save = "Shrani spremembe" scheduleEvery = "Zaženi vsakih" -sources = "Viri" -sourcesLoading = "Nalaganje virov..." trigger = "Sprožilec" triggerManual = "Samo ročno" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dni" hours = "ur" minutes = "minut" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Izbrisati \"{{name}}\"? Tega ni mogoče razveljaviti." cancel = "Prekliči" @@ -7867,6 +7825,37 @@ connectSource = "Poveži vir" description = "Ustvarite svoj prvi pipeline: izberite vire, na katerih se izvaja, povežite operacije in izberite, kam gre izhod." title = "Pipelines še ni" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktivni" paused = "Zaustavljeni" @@ -7879,7 +7868,6 @@ completed_one = "Zagon končan." completed_other = "Vseh {{count}} zagonov je končanih." empty = "Ničesar ni za zagnati: viri niso imeli dokumentov za obdelavo." failed = "Zagon ni uspel: {{error}}" -historyCleared = "Zgodovina počiščena. Naslednji zagon ponovno obdela vse, kar je trenutno v virih." inFlight = "Ni nič novega za zagon: dokumenti se še vedno obdelujejo iz prejšnjega zagona." parked_one = "Ni ničesar za zagon: {{count}} dokument je prej spodletel in je parkiran. Odpravite vzrok, nato počistite zgodovino, da poskusite znova." parked_other = "Ni ničesar za zagon: {{count}} dokumentov je prej spodletelo in so parkirani. Odpravite vzrok, nato počistite zgodovino, da poskusite znova." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Viri" status = "Stanje" steps = "Koraki" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Nadzor mape" @@ -8209,6 +8198,24 @@ label = "Priloži zadevi Jira" description = "Pošlje obdelani dokument kot prilogo." label = "Pošlji dokument po e-pošti (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Zapiše obdelani dokument v mapo." label = "Naloži v Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Dosežena omejitev" subtitle = "Vaš brezplačni preizkus traja do {{date}}. Kartica ni potrebna." title = "Enterprise preizkus" -[portal.search] -ariaLabel = "Iskanje" -placeholder = "Išči Stirling — endpointi, cevovodi, dokumenti…" - -[portal.search.empty] -noActionsDescription = "Hitra dejanja se bodo prikazala tukaj, ko bodo na voljo." -noActionsTitle = "Ni hitrih dejanj" -noMatches = "Ni zadetkov za \"{{query}}\"" -noMatchesDescription = "Poskusite z drugo ključno besedo ali prebrskajte katalog." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Prekliči" confirm = "Izbriši" title = "Izbriši vir?" -[portal.sources.empty] -description = "Povežite mapo (in kmalu shrambo v oblaku), da bodo vaši pravilniki imeli od kod pridobivati dokumente." -title = "Ni še povezanih virov" - [portal.sources.kpi] inUse = "V uporabi" total = "Povezave" @@ -8785,6 +8778,7 @@ unused = "Neuporabljeno" documents = "Dokumenti" source = "Vir" status = "Stanje" +type = "Type" usedBy = "Pravilniki" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Politike in zasebnost" [settings.preferences] title = "Nastavitve" -[settings.search] -placeholder = "Išči po straneh nastavitev..." - [settings.security] description = "Posodobite geslo, da ostane vaš račun varen." title = "Varnost" @@ -10128,6 +10119,7 @@ last = "Zadnja stran" maintainRatio = "Preklopi ohranjanje razmerja stranic" next = "Naslednja stran" noSavedSigs = "Ni shranjenih podpisov" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Osebni podpisi" previous = "Prejšnja stran" redo = "Ponovi" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Odstranjevanje ozadja ni uspelo" hint = "Naložite sliko podpisa v PNG ali JPG" label = "Naložite sliko podpisa" placeholder = "Izberite slikovno datoteko" +previewAlt = "Current image signature" processing = "Obdelava slike..." removeBackground = "Odstrani belo ozadje (naredi prozorno)" @@ -10179,6 +10172,17 @@ saved = "Zgoraj izberite shranjen podpis, nato kliknite kjer koli na PDF-ju, da text = "Ko zgoraj vnesete svoje ime, kliknite kjerkoli v PDF, da postavite podpis." title = "Kako dodati podpis" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Premakni podpis" pause = "Začasno ustavi postavljanje" @@ -10625,6 +10629,23 @@ title = "Naloži na strežnik" updateButton = "Posodobi na strežniku" uploadButton = "Naloži na strežnik" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Pojdi nazaj" pdfTools = "Orodja PDF" placeholder = "Izberite orodje za začetek" premiumFeature = "Premium funkcija:" -searchTools = "Iskanje orodij" toolsHeader = "Orodja" viewAllTools = "Prikaži vsa orodja" @@ -10822,6 +10842,9 @@ removal = "Odstranjevanje" signing = "Podpisovanje" verification = "Preverjanje" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Ni najdenih orodij" noTools = "Ni razpoložljivih orodij" @@ -10979,6 +11002,13 @@ approver = "Odobri pravilnik" editor = "Urejevalnik" processor = "Procesor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Prekličem povabilo za {{email}}? S trenutno povezavo se ne bodo mogli pridružiti." cancelInviteTitle = "Prekliči povabilo" @@ -10998,10 +11028,8 @@ title = "Ni še članov" addToTeam = "Dodaj v ekipo" guestCount = "{{count}} gost" guests = "Gostje" -guestsDesc = "Zunanji sodelavci, omejeni na to, kar ste delili. Samo urejevalnik." ledBy = "vodi {{owner}}" org = "Organizacija" -orgDesc = "Lastniki z avtoriteto za celotno organizacijo in odobritvijo pravilnikov" owners = "{{count}} lastnik" team = "Ekipa {{name}}" teamMeta = "{{count}} oseb" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Povabil/-a {{who}}" cancel = "Prekliči" -count = "{{count}} na čakanju" -desc = "Povabljene osebe, ki se še niso pridružile. Zasedajo mesto, dokler ne sprejmejo povabila." expiresInDays_one = "Poteče čez {{count}} dan" expiresInDays_other = "Poteče čez {{count}} dni" expiresToday = "Poteče danes" title = "Povabila na čakanju" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Pri dostopu do zalednega sistema je prišlo do napake ali pa nimate dostopa. Poskusite znova." title = "Članov ni bilo mogoče naložiti" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Išči priloge" title = "Priloge" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Naslov zaznamka" bookmarkTitleRequired = "Naslov zaznamka je obvezen" closeSidebar = "Zapri stransko vrstico zaznamkov" collapseAll = "Strni vse zaznamke" +empty = "No bookmarks in this document" expandAll = "Razširi vse zaznamke" searchPlaceholder = "Iskanje zaznamkov" +title = "Bookmarks" [viewer.comments] addComment = "Dodaj komentar" @@ -11264,10 +11297,12 @@ locateAnnotation = "Najdi v dokumentu" moreActions = "Več dejanj" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Stran {{page}}" placingHint = "Kliknite stran za postavitev… (prekliči)" removeCommentOnly = "Odstrani samo komentar" saveReply = "Shrani odgovor" +searchPlaceholder = "Search comments" title = "Komentarji" typeComment = "Komentar" typeInsertText = "Vstavi besedilo" @@ -11290,6 +11325,7 @@ unsavedDesc = "Imate neshranjene spremembe" closeSidebar = "Zapri stransko vrstico plasti" hideAll = "Skrij vse plasti" showAll = "Prikaži vse plasti" +title = "Layers" [viewer.link] delete = "Izbriši povezavo" @@ -11646,6 +11682,7 @@ exitRedaction = "Izhod iz načina redakcije" exportAll = "Izvozi PDF" exportSelected = "Izvozi izbrane strani" formFill = "Izpolni obrazec" +hideToolbar = "Hide toolbar" multiTool = "Večnamensko orodje" panMode = "Način premikanja" print = "Natisni PDF" @@ -11666,6 +11703,7 @@ selectAll = "Izberi vse" selectByNumber = "Izberi po številkah strani" selectLanguage = "Izberi jezik" share = "Delite" +showToolbar = "Show toolbar" toggleAnnotations = "Preklopi vidnost anotacij" toggleAttachments = "Preklopi priloge" toggleBookmarks = "Preklopi zaznamke" diff --git a/frontend/editor/public/locales/sr-LATN-RS/translation.toml b/frontend/editor/public/locales/sr-LATN-RS/translation.toml index f33616fc44..fb1ec724ae 100644 --- a/frontend/editor/public/locales/sr-LATN-RS/translation.toml +++ b/frontend/editor/public/locales/sr-LATN-RS/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabet" apply = "Primeni" applyAndContinue = "Sačuvaj i napusti" -areYouSure = "Da li ste sigurni da želite da napustite?" back = "Nazad" black = "Crno" blue = "Plavo" @@ -106,6 +105,7 @@ undoSuccess = "Operacija uspešno poništena" unknown = "Nepoznato" unpin = "Otkači datoteku (zameni nakon pokretanja alata)" unsavedChanges = "Imate nesačuvane promene u svom PDF-u." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Nesačuvane promene" unsupported = "Nije podržano" username = "Korisničko ime" @@ -1879,6 +1879,9 @@ width = "Širina" [app] description = "Besplatna alternativa Adobe Acrobat‑u (10M+ preuzimanja)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Konvertuj u PDF/A-3b" convertToPdfA3bDescription = "Kreira arhivski PDF sa ugrađenim prilozima" @@ -2933,12 +2936,16 @@ text = "Da biste ove dozvole učinili neizmenjivim, koristite alat Dodaj lozinku [chat.actions] copy = "Kopiraj poruku" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Opcije Stirling agenta" +assistant = "AI Assistant" clearChat = "Obriši čet" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Ova operacija će potrošiti vaše cloud kredite" tooltip = "Izaberi boju sa ekrana" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Izaberite boju" [common] +actions = "Actions" back = "Nazad" cancel = "Otkaži" close = "Zatvori" +codeSample = "Code sample" collapse = "Skupi" confirm = "Potvrdi" continue = "Nastavi" copied = "Kopirano!" copy = "Kopiraj" done = "Gotovo" +enterValue = "Enter value" error = "Greška" expand = "Proširi" loading = "Učitavanje..." next = "Sledeće" +open = "Open" preview = "Pregled" previous = "Prethodno" refresh = "Osveži" remaining = "Preostalo" retry = "Pokušaj ponovo" save = "Sačuvaj" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Obriši izabrano" @@ -3185,6 +3199,7 @@ title = "Metod kompresije" [compress.settings] desiredSize = "Željena veličina fajla" desiredSizePlaceholder = "Unesite veličinu" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Kompresija je lak način da smanjite veličinu fajla. Izaberite Veličina fajla da unesete ciljnu veličinu i da mi prilagodimo kvalitet za vas. Izaberite Kvalitet da ručno podesite jačinu kompresije." @@ -3387,6 +3402,24 @@ pdfOptions = "Opcije za PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Prvo izaberite izvorni format" settings = "Podešavanja" single = "Jedan" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Pretraga..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Uredi" @@ -3824,11 +3861,13 @@ shareSelected = "Podeli izabrano" sharing = "Deljenje" showAll = "Prikaži sve" showHistory = "Prikaži istoriju" +sortBy = "Sort files" sortByDate = "Sortiraj po datumu" sortByName = "Sortiraj po nazivu" sortBySize = "Sortiraj po veličini" storage = "Skladište" storageState = "Skladište" +storageUsed = "Storage used" synced = "Sinhronizovano" title = "Otpremi PDF datoteke" toolChain = "Primеnjeni alati" @@ -3874,8 +3913,12 @@ addFiles = "Dodaj datoteke" addingFiles = "Dodavanje fajlova…" collapse = "Skupi bočnu traku" customizeGroups = "Prilagodi grupe" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Otvorite datoteke da biste započeli" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Proširi bočnu traku" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive nije konfigurisan" @@ -3888,15 +3931,17 @@ openFromComputer = "Otvori sa računara" openSettings = "Otvori podešavanja" other = "Ostalo" recent = "Nedavno" -search = "Pretraga" -searchPlaceholder = "Pretraži datoteke..." viewAll = "Prikaži svih {{count}} fajlova" [fileSidebar.fileItem] closeViewer = "Zatvori pregledač" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Otvori u pregledaču" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Vrati na podrazumevano" subtitle = "Grupišite fajlove u nadređene kategorije. Dodajte postojeće ili nove oznake u kategoriju, preimenujte je ili kreirajte svoju. Fajlovi koji nisu ni u jednoj kategoriji prikazuju se pod „Ostalo”." title = "Kategorije bočne trake" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Dodaj u radni prostor" addToWorkspaceCount = "Dodaj {{count}} u radni prostor" @@ -3918,7 +3971,6 @@ backToMyFiles = "Nazad na Moje fajlove" breadcrumbs = "Putanja fascikle" cancel = "Otkaži" classification = "Klasifikacija" -clearSearch = "Obriši pretragu" clearSelection = "Poništi izbor" closeDetails = "Zatvori detalje" create = "Kreiraj" @@ -3950,6 +4002,7 @@ downloadAll = "Preuzmi sve" downloadVersion = "Preuzmi ovu verziju" dropOverlay = "Ispustite fajlove za otpremanje" dropOverlaySub = "Fajlovi počinju u Lokalno. Koristite 'Premesti u' ili 'Sačuvaj u oblaku' da ih organizujete u fasciklu." +duplicate = "Duplicate" file = "Fajl" fileInfo = "File info" fileMenu = "Radnje nad fajlom" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Promeni veličinu stabla fascikli (tasteri sa strelicama, Sh save = "Sačuvaj" saveToServer = "Sačuvaj na server" saveToServerDisabledHint = "Čuvanje na serveru nije omogućeno na ovom serveru. Zamolite administratora da ga uključi." -search = "Pretraga" -searchPlaceholder = "Pretraži ovu fasciklu i potfascikle" selectAll = "Izaberi sve" selectAllHint = "Kliknite da izaberete sve. Savet: držite Ctrl (ili Cmd) da dodajete fajlove jedan po jedan, Shift za izbor opsega." selectedCount = "{{count}} izabrano" @@ -4044,6 +4095,10 @@ title = "Još nema cloud fajlova" hint = "Fajlovi sačuvani bez otpremanja ostaju ovde. Ispustite fajl da dodate jedan." title = "Nema samo-lokalnih fajlova" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Fajlovi koje otvorite ili izmenite pojaviće se ovde." title = "Još ništa nije izmenjeno" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Nije moguće obrisati fasciklu." deleteFolderFailedDetail = "Nije moguće obrisati fasciklu: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Nije moguće ažurirati izgled fascikle." folderAppearanceFailedDetail = "Nije moguće ažurirati izgled fascikle: {{message}}" moveFilesFailed = "Nije moguće premestiti fajlove." @@ -4113,6 +4170,11 @@ localHint = "Samo skladišteno u ovom pregledaču" shared = "Podeljeno" sharedHint = "Podeljeno sa vama putem linka" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Najstarije prvo" modifiedDesc = "Najnovije prvo" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Nema fajlova u skladištu. Prvo otvorite neke fajlove." open = "Otvori" openFile = "Otvori fajl" openFiles = "Otvori fajlove" +selectFile = "Select {{name}}" selectFromStorage = "Izaberi iz skladišta" upload = "Otpremi" uploadFile = "Otpremi datoteku" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Sve je sačuvano" +analyzingFields = "Analysing form fields..." extractCsvError = "Izdvajanje CSV-a nije uspelo" extractXlsxError = "Izdvajanje XLSX-a nije uspelo" +filled = "filled" flattenAfterFilling = "Zapečati nakon popunjavanja" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obav" requiredFieldsError = "Popunite sva obavezna polja" rescanFields = "Ponovo skeniraj polja" @@ -4795,9 +4863,6 @@ title = "Slika u PDF" [imageToPdf] tags = "konverzija,img,jpg,slika,foto" -[infoBanner] -dismiss = "Odbaci" - [invite] acceptError = "Kreiranje naloga nije uspelo" accountFor = "Kreiranje naloga za" @@ -4979,6 +5044,34 @@ uploadSuccess = "Otpremanje uspešno!" uploadSuccessMessage = "Vaše slike su prenete." validating = "Provera sesije..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Skenirajte da otpremite fotografije. Slike se automatski konvertuju u PDF." descriptionNoConvert = "Skenirajte da otpremite fotografije sa mobilnog uređaja." @@ -4997,8 +5090,13 @@ title = "Otpremi sa mobilnog" tags = "Multi Alat,Multi operacija,Korisnički interfejs,klik i povuci,front end,klijentska strana,interaktivno,pomera" title = "PDF Multi Alatka" -[navbar] -search = "Pretraga" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Autentikacija nije uspela. Možete zatvoriti ovaj prozor i pokušati ponovo." @@ -5127,6 +5225,7 @@ activeFiles = "Prikaz Active Files pokazuje sve PDF-ove koje st allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Zatvori" cropSettings = "Sada kada smo izabrali fajl koji želimo da isečemo, možemo podesiti alat Crop da odaberemo oblast na koju želimo da isečemo PDF." +dialogLabel = "Onboarding" fileCheckbox = "Klikom na neki fajl birate ga za obradu. Možete izabrati više fajlova za grupne operacije." fileReplacement = "Izmenjeni fajl će automatski zameniti original u Workbench-u, što vam omogućava da ga lako obradite kroz dodatne alate." filesButton = "Dugme Files na traci brzog pristupa omogućava otpremanje PDF-ova na kojima ćete koristiti alate." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF u PDF/A" tags = "arhiva,dugoročno,standard,konverzija,čuvanje,čuvanje" title = "PDF u PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "štampa,standard,konverzija,produkcija,pretštampa,arhiva" title = "PDF u PDF/X" @@ -6045,6 +6151,7 @@ small = "500 kredita" xsmall = "100 kredita" [plan.availablePlans] +currency = "Billing currency" subtitle = "Izaberite paket koji odgovara vašim potrebama" title = "Dostupni paketi" @@ -6624,6 +6731,7 @@ reachedTitle = "Dostignut je mesečni limit potrošnje" title = "Nije moguće otvoriti Stripe portal" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "od {{allowance}} besplatnih PDF dokumenata iskorišćeno" capSuffix_other = "od {{allowance}} besplatnih PDF dokumenata iskorišćeno" eyebrow = "Processor probni period" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Pošaljite dokument ili obaveštenje email-om kada se pravilo pokrene." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Sačuvajte obrađene dokumente u Nextcloud." @@ -7077,11 +7211,6 @@ title = "Zvanični SDK-ovi" beta = "Beta" deprecated = "Zastarelo" -[portal.docs.search] -empty = "Nema odgovarajuće dokumentacije" -placeholder = "Pretraži dokumentaciju" -results = "{{count}} rezultata" - [portal.docs.skills] eyebrow = "VEŠTINE" lead = "Ugrađene, imenovane mogućnosti koje vaš agent poziva kao jedan alat. Svaka veština je deterministički lanac operacija sa priloženim evals." @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "Nijedan dokument ne odgovara ovom filteru." rowActions = "Radnje reda" sensitiveLabel = "Osetljivo" -sensitiveTitle = "Osetljivo — potreban pristup" [portal.documents.table.columns] action = "Procesni tok / Radnja" document = "Dokument" +labels = "Labels" product = "Proizvod" status = "Status" time = "Vreme" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Poslednji put viđeno" region = "Region" status = "Status" +target = "Target" version = "Verzija" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Ovaj prikaz je naišao na neočekivanu grešku. Pokušajte ponovo retry = "Pokušajte ponovo" title = "Nešto je pošlo naopako na ovoj stranici" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Nazad" body = "Preuzmite desktop aplikaciju ili self-hostujte server." @@ -7380,11 +7560,6 @@ cancel = "Otkaži" confirm = "Opozovi ključ" title = "Opozovi API ključ" -[portal.infrastructure.attestationLabel] -attested = "Atestirano" -inScope = "U opsegu" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtriraj audit događaje po kategoriji" heading = "Audit zapisi" @@ -7456,11 +7631,6 @@ info = "Informacija" success = "Uspeh" warning = "Upozorenje" -[portal.infrastructure.certLabel] -certified = "Sertifikovano" -inProgress = "U toku" -notStarted = "Nije započeto" - [portal.infrastructure.createKey] cancel = "Otkaži" createKey = "Kreiraj ključ" @@ -7474,238 +7644,10 @@ subtitleCreated = "Kopirajte ovu tajnu sada — neće biti ponovo prikazana." title = "Kreiraj API ključ" titleCreated = "Ključ je kreiran" -[portal.infrastructure.deployLabel] -live = "Uživo" -queued = "U redu čekanja" -rolledBack = "Vraćeno na prethodnu verziju" -rolling = "Uvođenje u toku" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Postavio" -environment = "Okruženje" -product = "Proizvod" -status = "Status" -version = "Verzija" -when = "Kada" - -[portal.infrastructure.deployments.recent] -heading = "Nedavna postavljanja" -subheading = "Najnovija uvođenja kroz proizvode i okruženja." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instance" -latency = "Latencija" -load = "Opterećenje" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Propusnost" -uptime = "Vreme rada" -version = "Verzija" - -[portal.infrastructure.deployments.regions] -heading = "Regioni" -subheading = "Uživo stanje svakog postavljenog Stirling regiona — latencija, opterećenje i verzija uvođenja." - -[portal.infrastructure.deployments.regions.empty] -description = "Postavljeni regioni će se ovde pojaviti kada vaš radni prostor bude obezbeđen." -title = "Nema postavljenih regiona" - [portal.infrastructure.keyLabel] active = "Aktivno" revoked = "Opozvano" -[portal.infrastructure.modelLabel] -active = "Aktivno" -degraded = "Degradirano" -disabled = "Onemogućeno" - -[portal.infrastructure.models] -heading = "Modeli" -msValue = "{{value}} ms" -subheading = "Katalog modela i rutiranje koje pokreće obradu dokumenata u vašem radnom prostoru." - -[portal.infrastructure.models.byom] -description = "Registrujte on-prem ili self-hosted model i vežite ga za region radi obrade u skladu sa rezidentnošću podataka." -title = "Donesite sopstveni model" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Upravljani modeli dostupni vašem radnom prostoru, sa latencijom i cenom uživo." -subEnterprise = "Upravljani, sopstveni i on-prem modeli — sa dostupnim vezivanjem po regionu." - -[portal.infrastructure.models.catalogue.empty] -description = "Modeli u katalogu vašeg radnog prostora pojavljuju se ovde." -title = "Nema dostupnih modela" - -[portal.infrastructure.models.columns] -cost = "Cena" -latency = "Latencija" -load = "Opterećenje" -model = "Model" -status = "Status" -type = "Tip" -version = "Verzija" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/poziv" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktivni modeli" -avgLatency = "Prosečna latencija" -included = "Uključeno" -monthlySpend = "Mesečna potrošnja za modele" - -[portal.infrastructure.models.routing] -empty = "Nema konfigurisanih pravila rutiranja." -heading = "Pravila rutiranja" -sub = "Koji model obrađuje svaku operaciju. Podrazumevano se primenjuje kada se ne poklopi uže pravilo." -subLocked = "Rutirajte operacije ka određenim modelima — dostupno na plaćenim planovima." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Nadogradite na Pro da kontrolišete koji model obrađuje svaku operaciju i tip dokumenta." -title = "Rutiranje modela je plaćena funkcija" - -[portal.infrastructure.models.routingColumns] -default = "Podrazumevano" -docType = "Tip dokumenta" -modelForAria = "Model za {{operation}}" -operation = "Operacija" -routedTo = "Rutirano ka" - -[portal.infrastructure.modelTypeLabel] -classification = "Klasifikacija" -extraction = "Ekstrakcija" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Degradirano" -down = "Nedostupno" -healthy = "Ispravno" - -[portal.infrastructure.security.access.byok] -description = "Obezbedite ključ iz sopstvenog KMS. Stirling šifruje njime, ali i dalje može da čita." -label = "Donesite sopstveni ključ (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Ključevi nikada ne napuštaju vaš KMS. Stirling čuva samo šifrovani tekst." -label = "Zadržite sopstveni ključ (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling upravlja ključevima za šifrovanje. Najjednostavnije — bez upravljanja ključevima sa vaše strane." -label = "Ključevi koje drži Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Politika pristupa dokumentima" -subheading = "Kontroliše ko može da dešifruje obrađene dokumente u mirovanju." - -[portal.infrastructure.security.attestations] -heading = "Potvrde usklađenosti" -noReport = "Nema dostupnog izveštaja" -subheading = "Audit položaj po okvirima, sa izveštajima dostupnim za potvrđene kontrole." -viewReport = "Prikaži izveštaj →" - -[portal.infrastructure.security.compliance] -heading = "Usklađenost" -subheading = "Potvrde i sertifikati koji pokrivaju Stirling platformu." - -[portal.infrastructure.security.empty] -description = "Bezbednosna konfiguracija vašeg radnog prostora će se ovde pojaviti." -title = "Bezbednosni položaj nije dostupan" - -[portal.infrastructure.security.hyokBanner] -description = "Uz HYOK, ključevi za šifrovanje nikada ne napuštaju vaš KMS. Stirling čuva i obrađuje samo šifrovani tekst koji možete opozvati u bilo kom trenutku." -title = "Stirling ne može da dešifruje vaše dokumente" - -[portal.infrastructure.security.ipAllowlist] -empty = "Nema konfigurisanih IP opsega — sve IP adrese su dozvoljene." -heading = "IP allowlist" -sub = "API pristup je ograničen na ove CIDR opsege." -subLocked = "Ograničite API pristup na poznate IP opsege — dostupno na plaćenim planovima." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Nadogradite na Pro da ograničite API pristup na određene mreže." -title = "IP allowlisting je plaćena funkcija" - -[portal.infrastructure.security.ipColumns] -added = "Dodato" -addedBy = "Dodao" -cidr = "CIDR" -label = "Oznaka" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "Upravljanje ključevima za šifrovanje" -keyId = "Identifikator ključa" -lastRotated = "Poslednja rotacija" -rotateKey = "Rotiraj ključ" -rotationPolicy = "Politika rotacije" -subheading = "Starateljstvo nad ključevima koji šifruju dokumente u mirovanju — ko može da dešifruje i kako se ključevi rotiraju." - -[portal.infrastructure.security.managedBanner] -description = "Starateljstvo po principu donesite sopstveni ključ (BYOK) i zadržite sopstveni ključ (HYOK) dostupno je na Enterprise planu. Nadogradite da obezbedite ključeve iz sopstvenog KMS." -title = "Ključevima na vašem planu upravlja Stirling" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Azijsko-pacifički region" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR granica podataka" -label = "Evropska unija" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Sjedinjene Države" - -[portal.infrastructure.security.residencyHeader] -heading = "Rezidentnost podataka" -subheading = "Gde se dokumenti čuvaju i obrađuju." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} iskorišćeno" - -[portal.infrastructure.storage.empty] -description = "Povezano skladište i korišćenje će se ovde pojaviti." -title = "Nema konfigurisanog skladišta" - -[portal.infrastructure.storage.lifecycle] -active = "Aktivno" -activeRange = "0–{{value}}d" -archived = "Arhivirano" -coldStorage = "hladno skladište" -deleted = "Obrisano" -never = "nikada" -purged = "trajno obrisano" - -[portal.infrastructure.storage.providers] -connect = "Poveži" -connected = "Povezano" -heading = "Povezani pružaoci" -subheading = "Gde se upisuju obrađeni artefakti." - -[portal.infrastructure.storage.retention] -heading = "Zadržavanje" -subheading = "Koliko dugo se artefakti čuvaju pre brisanja tokom životnog ciklusa." -windowLabel = "Podrazumevani period zadržavanja" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dan" -days_other = "{{count}} dana" -never = "Nikada ne briši" - -[portal.infrastructure.storage.totalUsage] -heading = "Ukupno korišćenje" -progressLabel = "Iskorišćeno skladište" -subheading = "Skladište potrošeno kroz sve povezane pružaoce." - [portal.infrastructure.tabs] apiKeys = "API ključevi" audit = "Audit zapisi" @@ -7715,11 +7657,9 @@ security = "Bezbednost" storage = "Skladište" [portal.integrations] -addAnother = "Dodaj još jednu" availableHeading = "Dostupno" comingSoonHeading = "Uskoro" connect = "Poveži" -connectedHeading = "Povezano" connectionCount_one = "{{count}} veza" connectionCount_other = "{{count}} veza" customApi = "Prilagođeni API" @@ -7737,6 +7677,10 @@ security = "Bezbednost" signing = "Potpisivanje" storage = "Skladište" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Povezano" @@ -7767,7 +7711,6 @@ integrations = "Integracije" pipelines = "Procesni tokovi" policies = "Politike" procurement = "Nabavka" -settings = "Podešavanja" sources = "Izvori" usage = "Korišćenje i naplata" users = "Korisnici" @@ -7784,7 +7727,7 @@ title = "Procesni tokovi" newPipeline = "Novi procesni tok" [portal.pipelines.builder] -addStep = "Dodaj alat" +activate = "Activate" back = "Nazad na procesne tokove" cannotFollow = "Ne može da prihvati {{produced}}" chooseAccount = "Izaberite nalog" @@ -7792,28 +7735,38 @@ chooseDestination = "Izaberite odredište" chooseOperation = "Izaberite šta ovaj korak radi" chooseSource = "Izaberite izvor" discard = "Odbaci izmene" -enabled = "Omogućeno" inputs = "Unos" inputSource = "Izvor unosa" inputTrigger = "Okidač" keepEditing = "Nastavi uređivanje" +moreActions = "More actions" needsConfiguring = "Potrebno je podešavanje" -needsUpload = "Potrebna je otpremljena datoteka" -noSources = "Još nema povezanih izvora. Kreirajte jedan da biste ga koristili kao unos." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Nijedan alat ne odgovara vašoj pretrazi." -pipelineSettings = "Podešavanja procesnog toka" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Pretraži alate" -selectToolBody = "Dodajte alat da napravite procesni tok." -selectToolTitle = "Još nema alata" sendToSystem = "Pošalji u drugi sistem" stepsIncompatible = "Ovi koraci ne mogu da se pokrenu na onome što njihov prethodni korak proizvodi: {{tools}}." stepsNeedSetup = "Ove korake i dalje treba podesiti pre čuvanja: {{tools}}." -toolSettings = "Podešavanja alata" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Neprepoznata operacija, zadržana kakva jeste." unsavedBody = "Imate nesačuvane izmene. Sačuvajte ih pre napuštanja ili ih odbacite?" unsavedTitle = "Nesačuvane izmene" -uploadUnsupported = "Otpremljene datoteke još nisu podržane u procesnim tokovima, pa ovi koraci ne mogu biti sačuvani: {{tools}}." usesDefaults = "Pokreće se sa podrazumevanim podešavanjima" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kombinuje svaku datoteku iz prethodnog koraka" @@ -7825,31 +7778,36 @@ undeclared-operation = "Nije moguće proveriti šta ovaj korak prihvata" [portal.pipelines.composer] addTool = "Dodaj alat" -cancel = "Otkaži" -chainEmpty = "Dodajte alat da počnete da pravite procesni tok." create = "Kreiraj procesni tok" +createPaused = "Create paused" editingUnsupported = "Prikaz ovih parametara alata za uređivanje još nije podržan." -moveDown = "Pomeri dole" -moveUp = "Pomeri gore" +editSource = "Edit source" name = "Naziv" namePlaceholder = "npr. Prolaz za redakciju" noToolSettings = "Ovaj alat nema podesiva podešavanja." -operations_one = "Operacija ({{count}})" -operations_other = "Operacije ({{count}})" output = "Izlaz" -removeStep = "Ukloni operaciju" save = "Sačuvaj izmene" scheduleEvery = "Pokreni svakih" -sources = "Izvori" -sourcesLoading = "Učitavanje izvora..." trigger = "Okidač" triggerManual = "Samo ručno" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dana" hours = "sati" minutes = "minuta" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Obrisati \"{{name}}\"? Ovo se ne može opozvati." cancel = "Otkaži" @@ -7867,6 +7825,37 @@ connectSource = "Poveži izvor" description = "Kreirajte svoj prvi procesni tok: izaberite izvore nad kojima se pokreće, povežite operacije u lanac i izaberite gde izlaz ide." title = "Još nema procesnih tokova" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktivni" paused = "Pauzirani" @@ -7879,7 +7868,6 @@ completed_one = "Pokretanje je završeno." completed_other = "Svih {{count}} pokretanja je završeno." empty = "Nema ničega za pokretanje: izvori nisu imali dokumente za obradu." failed = "Pokretanje nije uspelo: {{error}}" -historyCleared = "Istorija je obrisana. Sledeće pokretanje ponovo obrađuje sve što se trenutno nalazi u izvorima." inFlight = "Nema ničeg novog za pokretanje: dokumenti se još obrađuju iz ranijeg pokretanja." parked_one = "Nema šta da se pokrene: {{count}} dokument prethodno nije uspeo i parkiran je. Otklonite uzrok, zatim obrišite istoriju da biste pokušali ponovo." parked_other = "Nema šta da se pokrene: {{count}} dokumenata prethodno nije uspelo i parkirani su. Otklonite uzrok, zatim obrišite istoriju da biste pokušali ponovo." @@ -7895,6 +7883,7 @@ name = "Procesni tok" sources = "Izvori" status = "Status" steps = "Koraci" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Nadzor foldera" @@ -8209,6 +8198,24 @@ label = "Priloži uz Jira zadatak" description = "Šalje obrađeni dokument kao prilog." label = "Pošalji dokument email-om (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Upisuje obrađeni dokument u fasciklu." label = "Otpremi u Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Maksimalno iskorišćeno" subtitle = "Vaš besplatni probni period traje do {{date}}. Kartica nije potrebna." title = "Enterprise probni period" -[portal.search] -ariaLabel = "Pretraga" -placeholder = "Pretražite Stirling — endpoint-i, tokovi obrade, dokumenti…" - -[portal.search.empty] -noActionsDescription = "Brze radnje će se pojaviti ovde kada budu dostupne." -noActionsTitle = "Nema brzih radnji" -noMatches = "Nema poklapanja za \"{{query}}\"" -noMatchesDescription = "Pokušajte sa drugom ključnom reči ili pregledajte katalog." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Otkaži" confirm = "Izbriši" title = "Izbrisati izvor?" -[portal.sources.empty] -description = "Povežite fasciklu (a uskoro i cloud skladište) kako bi vaše politike imale odakle da preuzimaju dokumente." -title = "Još nema povezanih izvora" - [portal.sources.kpi] inUse = "U upotrebi" total = "Veze" @@ -8785,6 +8778,7 @@ unused = "Neiskorišćeno" documents = "Dokumenti" source = "Izvor" status = "Status" +type = "Type" usedBy = "Politike" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Politike i privatnost" [settings.preferences] title = "Preferencije" -[settings.search] -placeholder = "Pretražite stranice podešavanja..." - [settings.security] description = "Ažurirajte lozinku da biste svoj nalog održali bezbednim." title = "Bezbednost" @@ -10128,6 +10119,7 @@ last = "Poslednja strana" maintainRatio = "Uključi/isključi zadržavanje proporcija" next = "Sledeća strana" noSavedSigs = "Nema snimljenih potpisa" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Lični potpisi" previous = "Prethodna strana" redo = "Ponovi" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Uklanjanje pozadine nije uspelo" hint = "Otpremite PNG ili JPG sliku svog potpisa" label = "Otpremite sliku potpisa" placeholder = "Izaberite slikovnu datoteku" +previewAlt = "Current image signature" processing = "Obrada slike..." removeBackground = "Ukloni belu pozadinu (učini providnom)" @@ -10179,6 +10172,17 @@ saved = "Izaberite sačuvani potpis iznad, zatim kliknite bilo gde na PDF da ga text = "Nakon što unesete svoje ime iznad, kliknite bilo gde na PDF da postavite svoj potpis." title = "Kako dodati potpis" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Pomeraj potpis" pause = "Pauziraj postavljanje" @@ -10625,6 +10629,23 @@ title = "Otpremi na server" updateButton = "Ažuriraj na serveru" uploadButton = "Otpremi na server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Idi nazad" pdfTools = "PDF alati" placeholder = "Izaberite alat da biste započeli" premiumFeature = "Premium funkcija:" -searchTools = "Pretraži alate" toolsHeader = "Alati" viewAllTools = "Prikaži sve alate" @@ -10822,6 +10842,9 @@ removal = "Uklanjanje" signing = "Potpisivanje" verification = "Verifikacija" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Nema pronađenih alata" noTools = "Nema dostupnih alata" @@ -10979,6 +11002,13 @@ approver = "Odobrava politiku" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Otkažite poziv za {{email}}? Ova osoba neće moći da se pridruži pomoću trenutnog linka." cancelInviteTitle = "Otkaži poziv" @@ -10998,10 +11028,8 @@ title = "Još nema članova" addToTeam = "Dodaj u tim" guestCount = "{{count}} gost" guests = "Gosti" -guestsDesc = "Spoljni saradnici, ograničeni na ono što ste podelili. Samo Editor." ledBy = "vodi {{owner}}" org = "Organizacija" -orgDesc = "Vlasnici sa ovlašćenjima na nivou organizacije i odobravanjem politika" owners = "{{count}} vlasnik" team = "{{name}} tim" teamMeta = "{{count}} osoba" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Pozvao/la {{who}}" cancel = "Otkaži" -count = "{{count}} na čekanju" -desc = "Pozvane osobe koje se još nisu pridružile. Zauzimaju mesto dok ne prihvate." expiresInDays_one = "Ističe za {{count}} dan" expiresInDays_other = "Ističe za {{count}} dana" expiresToday = "Ističe danas" title = "Pozivi na čekanju" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Nešto je pošlo naopako pri kontaktiranju backend-a ili nemate pristup. Pokušajte ponovo." title = "Nije moguće učitati članove" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Pretraži priloge" title = "Prilozi" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Naslov obeleživača" bookmarkTitleRequired = "Naslov obeleživača je obavezan" closeSidebar = "Zatvori bočnu traku obeleživača" collapseAll = "Skupi sve obeleživače" +empty = "No bookmarks in this document" expandAll = "Proširi sve obeleživače" searchPlaceholder = "Pretraži obeleživače" +title = "Bookmarks" [viewer.comments] addComment = "Dodaj komentar" @@ -11264,10 +11297,12 @@ locateAnnotation = "Pronađi u dokumentu" moreActions = "Više radnji" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Stranica {{page}}" placingHint = "Kliknite na stranicu da postavite… (otkaži)" removeCommentOnly = "Ukloni samo komentar" saveReply = "Sačuvaj odgovor" +searchPlaceholder = "Search comments" title = "Komentari" typeComment = "Komentar" typeInsertText = "Umetni tekst" @@ -11290,6 +11325,7 @@ unsavedDesc = "Imate nesačuvane promene" closeSidebar = "Zatvori bočnu traku slojeva" hideAll = "Sakrij sve slojeve" showAll = "Prikaži sve slojeve" +title = "Layers" [viewer.link] delete = "Obriši link" @@ -11646,6 +11682,7 @@ exitRedaction = "Izađi iz režima zacrnjivanja" exportAll = "Izvezi PDF" exportSelected = "Izvezi izabrane stranice" formFill = "Popuni obrazac" +hideToolbar = "Hide toolbar" multiTool = "Višenamenski alat" panMode = "Režim pomeranja" print = "Štampaj PDF" @@ -11666,6 +11703,7 @@ selectAll = "Izaberi sve" selectByNumber = "Izaberi po brojevima stranica" selectLanguage = "Izaberi jezik" share = "Podeli" +showToolbar = "Show toolbar" toggleAnnotations = "Uključi/isključi vidljivost anotacija" toggleAttachments = "Uključi/isključi priloge" toggleBookmarks = "Uključi/isključi obeleživače" diff --git a/frontend/editor/public/locales/sv-SE/translation.toml b/frontend/editor/public/locales/sv-SE/translation.toml index fd7cf0218b..2576b3bd96 100644 --- a/frontend/editor/public/locales/sv-SE/translation.toml +++ b/frontend/editor/public/locales/sv-SE/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabet" apply = "Verkställ" applyAndContinue = "Spara och lämna" -areYouSure = "Är du säker på att du vill lämna?" back = "Tillbaka" black = "Svart" blue = "Blå" @@ -106,6 +105,7 @@ undoSuccess = "Åtgärden ångrades" unknown = "Okänt" unpin = "Lossa fil (ersätt efter verktygskörning)" unsavedChanges = "Du har osparade ändringar i din PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Osparade ändringar" unsupported = "Stöds inte" username = "Användarnamn" @@ -1879,6 +1879,9 @@ width = "Bredd" [app] description = "Det kostnadsfria alternativet till Adobe Acrobat (10M+ nedladdningar)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Konvertera till PDF/A-3b" convertToPdfA3bDescription = "Skapar en arkiv-PDF med inbäddade bilagor" @@ -2933,12 +2936,16 @@ text = "För att göra dessa behörigheter oföränderliga, använd verktyget L [chat.actions] copy = "Kopiera meddelande" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Alternativ för Stirling-agent" +assistant = "AI Assistant" clearChat = "Rensa chatt" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Denna åtgärd kommer att använda dina molnkrediter" tooltip = "Välj färg från skärmen" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Välj färg" [common] +actions = "Actions" back = "Tillbaka" cancel = "Avbryt" close = "Stäng" +codeSample = "Code sample" collapse = "Fäll ihop" confirm = "Bekräfta" continue = "Fortsätt" copied = "Kopierat!" copy = "Kopiera" done = "Klar" +enterValue = "Enter value" error = "Fel" expand = "Fäll ut" loading = "Läser in..." next = "Nästa" +open = "Open" preview = "Förhandsgranska" previous = "Föregående" refresh = "Uppdatera" remaining = "Återstår" retry = "Försök igen" save = "Spara" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Rensa valda" @@ -3185,6 +3199,7 @@ title = "Komprimeringsmetod" [compress.settings] desiredSize = "Önskad filstorlek" desiredSizePlaceholder = "Ange storlek" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Komprimering är ett enkelt sätt att minska filstorleken. Välj Filstorlek för att ange en målstorlek så justerar vi kvaliteten åt dig. Välj Kvalitet för att ställa in komprimeringsstyrkan manuellt." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF‑alternativ" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Välj först ett källformat" settings = "Inställningar" single = "En" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Sök..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Redigera" @@ -3824,11 +3861,13 @@ shareSelected = "Dela valda" sharing = "Delning" showAll = "Visa alla" showHistory = "Visa historik" +sortBy = "Sort files" sortByDate = "Sortera efter datum" sortByName = "Sortera efter namn" sortBySize = "Sortera efter storlek" storage = "Lagring" storageState = "Lagring" +storageUsed = "Storage used" synced = "Synkroniserad" title = "Ladda upp PDF-filer" toolChain = "Tillämpade verktyg" @@ -3874,8 +3913,12 @@ addFiles = "Lägg till filer" addingFiles = "Lägger till filer…" collapse = "Fäll ihop sidofält" customizeGroups = "Anpassa grupper" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Öppna filer för att komma igång" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Expandera sidofält" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive är inte konfigurerat" @@ -3888,15 +3931,17 @@ openFromComputer = "Öppna från dator" openSettings = "Öppna inställningar" other = "Annat" recent = "Senaste" -search = "Sök" -searchPlaceholder = "Sök filer..." viewAll = "Visa alla {{count}} filer" [fileSidebar.fileItem] closeViewer = "Stäng visare" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Öppna i visare" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Återställ till standard" subtitle = "Gruppera dina filer i överordnade kategorier. Lägg till befintliga eller nya etiketter i en kategori, byt namn på den eller skapa en egen. Filer som inte finns i någon av dina kategorier visas under ”Annat”." title = "Sidofältskategorier" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Lägg till i arbetsyta" addToWorkspaceCount = "Lägg till {{count}} i arbetsyta" @@ -3918,7 +3971,6 @@ backToMyFiles = "Tillbaka till Mina filer" breadcrumbs = "Mappsökväg" cancel = "Avbryt" classification = "Klassificering" -clearSearch = "Rensa sökning" clearSelection = "Rensa markering" closeDetails = "Stäng detaljer" create = "Skapa" @@ -3950,6 +4002,7 @@ downloadAll = "Ladda ner alla" downloadVersion = "Ladda ner den här versionen" dropOverlay = "Släpp filer för att ladda upp" dropOverlaySub = "Filer börjar i Lokalt. Använd 'Flytta till' eller 'Spara till molnet' för att organisera dem i en mapp." +duplicate = "Duplicate" file = "Fil" fileInfo = "File info" fileMenu = "Filåtgärder" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Ändra storlek på mappträdet (piltangenter, Skift för st save = "Spara" saveToServer = "Spara till servern" saveToServerDisabledHint = "Spara till servern är inte aktiverat på denna server. Be din administratör att aktivera det." -search = "Sök" -searchPlaceholder = "Sök i den här mappen och undermappar" selectAll = "Markera alla" selectAllHint = "Klicka för att markera alla. Tips: håll Ctrl (eller Cmd) för att lägga till filer en i taget, Skift för att markera ett intervall." selectedCount = "{{count}} markerade" @@ -4044,6 +4095,10 @@ title = "Inga molnfiler ännu" hint = "Filer som sparas utan uppladdning stannar här. Släpp en fil för att lägga till en." title = "Inga lokala filer" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Filer du öppnar eller redigerar visas här." title = "Inget ändrat ännu" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Kunde inte ta bort mappen." deleteFolderFailedDetail = "Kunde inte ta bort mappen: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Kunde inte uppdatera mappens utseende." folderAppearanceFailedDetail = "Kunde inte uppdatera mappens utseende: {{message}}" moveFilesFailed = "Kunde inte flytta filer." @@ -4113,6 +4170,11 @@ localHint = "Endast lagrad i denna webbläsare" shared = "Delad" sharedHint = "Delad med dig via länk" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Äldst först" modifiedDesc = "Senaste först" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Inga filer tillgängliga i lagringen. Öppna några file open = "Öppna" openFile = "Öppna fil" openFiles = "Öppna filer" +selectFile = "Select {{name}}" selectFromStorage = "Välj från lagring" upload = "Ladda upp" uploadFile = "Ladda upp fil" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Allt sparat" +analyzingFields = "Analysing form fields..." extractCsvError = "Kunde inte extrahera CSV" extractXlsxError = "Kunde inte extrahera XLSX" +filled = "filled" flattenAfterFilling = "Platta ut efter ifyllning" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "obl" requiredFieldsError = "Fyll i alla obligatoriska fält" rescanFields = "Skanna fält igen" @@ -4795,9 +4863,6 @@ title = "Bild till PDF" [imageToPdf] tags = "konvertering,img,jpg,bild,foto" -[infoBanner] -dismiss = "Avfärda" - [invite] acceptError = "Misslyckades med att skapa konto" accountFor = "Skapar konto för" @@ -4979,6 +5044,34 @@ uploadSuccess = "Uppladdning lyckades!" uploadSuccessMessage = "Dina bilder har överförts." validating = "Validerar session..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Skanna för att ladda upp foton. Bilder konverteras automatiskt till PDF." descriptionNoConvert = "Skanna för att ladda upp foton från din mobila enhet." @@ -4997,8 +5090,13 @@ title = "Ladda upp från mobil" tags = "Multiverktyg,Multioperation,UI,klicka dra,front end,klientsida" title = "PDF-multiverktyg" -[navbar] -search = "Sök" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Autentiseringen lyckades inte. Du kan stänga detta fönster och försöka igen." @@ -5127,6 +5225,7 @@ activeFiles = "Vyn Aktiva filer visar alla PDF:er du har laddat allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Stäng" cropSettings = "Nu när vi har valt filen vi vill beskära kan vi konfigurera Beskär-verktyget för att välja området som vi vill beskära PDF-filen till." +dialogLabel = "Onboarding" fileCheckbox = "Att klicka på en av filerna markerar den för bearbetning. Du kan välja flera filer för batchåtgärder." fileReplacement = "Den ändrade filen ersätter originalfilen i Arbetsbänken automatiskt, vilket gör det enkelt att köra den genom fler verktyg." filesButton = "Knappen Filer på snabbåtkomstfältet låter dig ladda upp PDF-filer att använda verktygen på." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF till PDF/A" tags = "arkiv,långtids,standard,konvertering,lagring,bevarande" title = "PDF till PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "tryck,standard,konvertering,produktion,prepress,arkiv" title = "PDF till PDF/X" @@ -6045,6 +6151,7 @@ small = "500 krediter" xsmall = "100 krediter" [plan.availablePlans] +currency = "Billing currency" subtitle = "Välj den plan som passar dina behov" title = "Tillgängliga planer" @@ -6624,6 +6731,7 @@ reachedTitle = "Månatlig utgiftsgräns nådd" title = "Det gick inte att öppna Stripe-portalen" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "av {{allowance}} gratis PDF:er använda" capSuffix_other = "av {{allowance}} gratis PDF:er använda" eyebrow = "Processor-provperiod" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Skicka ett dokument eller en avisering via e-post när en policy körs." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Lagra bearbetade dokument i Nextcloud." @@ -7077,11 +7211,6 @@ title = "Officiella SDK:er" beta = "Beta" deprecated = "Föråldrad" -[portal.docs.search] -empty = "Inga matchande dokument" -placeholder = "Sök i dokumentation" -results = "{{count}} resultat" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Medföljande, namngivna förmågor som din agent anropar som ett enda verktyg. Varje skill är en deterministisk åtgärdskedja med kopplade evals." @@ -7180,11 +7309,11 @@ editorAction = "Redigerare" empty = "Inga dokument matchar det här filtret." rowActions = "Radåtgärder" sensitiveLabel = "Känsligt" -sensitiveTitle = "Känsligt — åtkomst krävs" [portal.documents.table.columns] action = "Pipeline / Åtgärd" document = "Dokument" +labels = "Labels" product = "Produkt" status = "Status" time = "Tid" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Senast sedd" region = "Region" status = "Status" +target = "Target" version = "Version" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Den här vyn stötte på ett oväntat fel. Försök igen eller v retry = "Försök igen" title = "Något gick fel på den här sidan" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Tillbaka" body = "Ladda ner desktop-appen eller självhosta servern." @@ -7380,11 +7560,6 @@ cancel = "Avbryt" confirm = "Återkalla nyckel" title = "Återkalla API-nyckel" -[portal.infrastructure.attestationLabel] -attested = "Intygad" -inScope = "Inom omfång" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Filtrera granskningshändelser efter kategori" heading = "Granskningsloggar" @@ -7456,11 +7631,6 @@ info = "Info" success = "Klart" warning = "Varning" -[portal.infrastructure.certLabel] -certified = "Certifierad" -inProgress = "Pågår" -notStarted = "Inte startad" - [portal.infrastructure.createKey] cancel = "Avbryt" createKey = "Skapa nyckel" @@ -7474,238 +7644,10 @@ subtitleCreated = "Kopiera den här hemligheten nu — den visas inte igen." title = "Skapa API-nyckel" titleCreated = "Nyckel skapad" -[portal.infrastructure.deployLabel] -live = "Live" -queued = "Köad" -rolledBack = "Återställd" -rolling = "Rullas ut" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Distribuerad av" -environment = "Miljö" -product = "Produkt" -status = "Status" -version = "Version" -when = "När" - -[portal.infrastructure.deployments.recent] -heading = "Senaste distributioner" -subheading = "De senaste utrullningarna över produkter och miljöer." - -[portal.infrastructure.deployments.regionColumns] -instances = "Instanser" -latency = "Latens" -load = "Belastning" -p99 = "P99" -region = "Region" -status = "Status" -throughput = "Genomströmning" -uptime = "Drifttid" -version = "Version" - -[portal.infrastructure.deployments.regions] -heading = "Regioner" -subheading = "Livehälsa för varje distribuerad Stirling-region — latens, belastning och utrullningsversion." - -[portal.infrastructure.deployments.regions.empty] -description = "Distribuerade regioner visas här när din arbetsyta har provisionerats." -title = "Inga regioner distribuerade" - [portal.infrastructure.keyLabel] active = "Aktiv" revoked = "Återkallad" -[portal.infrastructure.modelLabel] -active = "Aktiv" -degraded = "Försämrad" -disabled = "Inaktiverad" - -[portal.infrastructure.models] -heading = "Modeller" -msValue = "{{value}} ms" -subheading = "Modellkatalogen och routingen som driver dokumentbearbetning i din arbetsyta." - -[portal.infrastructure.models.byom] -description = "Registrera en lokal eller självhostad modell och fäst den vid en region för behandling som är bunden till dataresidens." -title = "Ta med din egen modell" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Hanterade modeller tillgängliga för din arbetsyta, med live-latens och kostnad." -subEnterprise = "Hanterade, egna och lokala modeller — med fästning per region tillgänglig." - -[portal.infrastructure.models.catalogue.empty] -description = "Modeller i din arbetsytas katalog visas här." -title = "Inga modeller tillgängliga" - -[portal.infrastructure.models.columns] -cost = "Kostnad" -latency = "Latens" -load = "Belastning" -model = "Modell" -status = "Status" -type = "Typ" -version = "Version" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/anrop" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktiva modeller" -avgLatency = "Genomsnittlig latens" -included = "Inkluderat" -monthlySpend = "Månatlig modellkostnad" - -[portal.infrastructure.models.routing] -empty = "Inga routingregler konfigurerade." -heading = "Routingregler" -sub = "Vilken modell som hanterar varje åtgärd. Standardvärdet används när ingen smalare regel matchar." -subLocked = "Routa åtgärder till specifika modeller — tillgängligt med betalplaner." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Uppgradera till Pro för att styra vilken modell som hanterar varje åtgärd och dokumenttyp." -title = "Modellrouting är en betalfunktion" - -[portal.infrastructure.models.routingColumns] -default = "Standard" -docType = "Dokumenttyp" -modelForAria = "Modell för {{operation}}" -operation = "Åtgärd" -routedTo = "Routas till" - -[portal.infrastructure.modelTypeLabel] -classification = "Klassificering" -extraction = "Extrahering" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Försämrad" -down = "Nere" -healthy = "Felfri" - -[portal.infrastructure.security.access.byok] -description = "Tillhandahåll en nyckel från din egen KMS. Stirling krypterar med den men kan fortfarande läsa." -label = "Ta med din egen nyckel (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Nycklar lämnar aldrig din KMS. Stirling lagrar endast chiffertext." -label = "Behåll din egen nyckel (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling hanterar krypteringsnycklar. Enklast — ingen nyckeldrift på din sida." -label = "Nycklar som hålls av Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Policy för dokumentåtkomst" -subheading = "Styr vem som kan dekryptera bearbetade dokument i vila." - -[portal.infrastructure.security.attestations] -heading = "Efterlevnadsintyg" -noReport = "Ingen rapport tillgänglig" -subheading = "Granskningsläge per ramverk, med rapporter tillgängliga för intygade kontroller." -viewReport = "Visa rapport →" - -[portal.infrastructure.security.compliance] -heading = "Efterlevnad" -subheading = "Intyg och certifieringar som täcker Stirling-plattformen." - -[portal.infrastructure.security.empty] -description = "Din arbetsytas säkerhetskonfiguration visas här." -title = "Säkerhetsläge inte tillgängligt" - -[portal.infrastructure.security.hyokBanner] -description = "Med HYOK lämnar krypteringsnycklar aldrig din KMS. Stirling lagrar och bearbetar endast chiffertext som du kan återkalla när som helst." -title = "Stirling kan inte dekryptera dina dokument" - -[portal.infrastructure.security.ipAllowlist] -empty = "Inga IP-intervall konfigurerade — alla IP-adresser tillåtna." -heading = "IP-tillåtelselista" -sub = "API-åtkomst är begränsad till dessa CIDR-intervall." -subLocked = "Begränsa API-åtkomst till kända IP-intervall — tillgängligt med betalplaner." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Uppgradera till Pro för att begränsa API-åtkomst till specifika nätverk." -title = "IP-tillåtelselista är en betalfunktion" - -[portal.infrastructure.security.ipColumns] -added = "Tillagd" -addedBy = "Tillagd av" -cidr = "CIDR" -label = "Etikett" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritm" -heading = "Hantering av krypteringsnycklar" -keyId = "Nyckelidentifierare" -lastRotated = "Senast roterad" -rotateKey = "Rotera nyckel" -rotationPolicy = "Rotationspolicy" -subheading = "Förvaring av nycklarna som krypterar dokument i vila — vem som kan dekryptera och hur nycklar roteras." - -[portal.infrastructure.security.managedBanner] -description = "Förvaring med bring-your-own-key (BYOK) och hold-your-own-key (HYOK) är tillgänglig i Enterprise. Uppgradera för att tillhandahålla nycklar från din egen KMS." -title = "Nycklar hanteras av Stirling i din plan" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asien och Stillahavsområdet" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR-datagräns" -label = "Europeiska unionen" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "USA" - -[portal.infrastructure.security.residencyHeader] -heading = "Dataresidens" -subheading = "Var dokument lagras och bearbetas." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} används" - -[portal.infrastructure.storage.empty] -description = "Ansluten lagring och användning visas här." -title = "Ingen lagring konfigurerad" - -[portal.infrastructure.storage.lifecycle] -active = "Aktiv" -activeRange = "0–{{value}} d" -archived = "Arkiverad" -coldStorage = "kallagring" -deleted = "Borttagen" -never = "aldrig" -purged = "rensad" - -[portal.infrastructure.storage.providers] -connect = "Anslut" -connected = "Ansluten" -heading = "Anslutna leverantörer" -subheading = "Där bearbetade artefakter skrivs." - -[portal.infrastructure.storage.retention] -heading = "Lagringstid" -subheading = "Hur länge artefakter behålls innan livscykelborttagning." -windowLabel = "Standardfönster för lagringstid" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} dag" -days_other = "{{count}} dagar" -never = "Ta aldrig bort" - -[portal.infrastructure.storage.totalUsage] -heading = "Total användning" -progressLabel = "Lagring använd" -subheading = "Lagring som förbrukats över alla anslutna leverantörer." - [portal.infrastructure.tabs] apiKeys = "API-nycklar" audit = "Granskningsloggar" @@ -7715,11 +7657,9 @@ security = "Säkerhet" storage = "Lagring" [portal.integrations] -addAnother = "Lägg till en till" availableHeading = "Tillgängliga" comingSoonHeading = "Kommer snart" connect = "Anslut" -connectedHeading = "Anslutna" connectionCount_one = "{{count}} anslutning" connectionCount_other = "{{count}} anslutningar" customApi = "Anpassat API" @@ -7737,6 +7677,10 @@ security = "Säkerhet" signing = "Signering" storage = "Lagring" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Ansluten" @@ -7767,7 +7711,6 @@ integrations = "Integrationer" pipelines = "Pipelines" policies = "Policyer" procurement = "Inköp" -settings = "Inställningar" sources = "Källor" usage = "Användning och fakturering" users = "Användare" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Ny pipeline" [portal.pipelines.builder] -addStep = "Lägg till verktyg" +activate = "Activate" back = "Tillbaka till pipelines" cannotFollow = "Kan inte ta emot {{produced}}" chooseAccount = "Välj ett konto" @@ -7792,28 +7735,38 @@ chooseDestination = "Välj en destination" chooseOperation = "Välj vad det här steget gör" chooseSource = "Välj en källa" discard = "Kassera ändringar" -enabled = "Aktiverad" inputs = "Indata" inputSource = "Indatakälla" inputTrigger = "Utlösare" keepEditing = "Fortsätt redigera" +moreActions = "More actions" needsConfiguring = "Behöver konfigureras" -needsUpload = "Behöver en uppladdad fil" -noSources = "Inga källor är anslutna än. Skapa en för att använda den som indata." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Inga verktyg matchar din sökning." -pipelineSettings = "Pipeline-inställningar" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Sök verktyg" -selectToolBody = "Lägg till ett verktyg för att bygga din pipeline." -selectToolTitle = "Inga verktyg ännu" sendToSystem = "Skicka till ett annat system" stepsIncompatible = "Dessa steg kan inte köras på det som deras föregående steg producerar: {{tools}}." stepsNeedSetup = "Dessa steg behöver fortfarande konfigureras innan du sparar: {{tools}}." -toolSettings = "Verktygsinställningar" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Okänd åtgärd, behålls som den är." unsavedBody = "Du har osparade ändringar. Spara dem innan du lämnar, eller kassera dem?" unsavedTitle = "Osparade ändringar" -uploadUnsupported = "Uppladdade filer stöds inte i pipelines ännu, så de här stegen kan inte sparas: {{tools}}." usesDefaults = "Körs med standardinställningar" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kombinerar varje fil från föregående steg" @@ -7825,31 +7778,36 @@ undeclared-operation = "Kan inte kontrollera vad det här steget accepterar" [portal.pipelines.composer] addTool = "Lägg till verktyg" -cancel = "Avbryt" -chainEmpty = "Lägg till ett verktyg för att börja bygga din pipeline." create = "Skapa pipeline" +createPaused = "Create paused" editingUnsupported = "Visning av dessa verktygsparametrar för redigering stöds inte ännu." -moveDown = "Flytta ned" -moveUp = "Flytta upp" +editSource = "Edit source" name = "Namn" namePlaceholder = "t.ex. Maskeringsgenomgång" noToolSettings = "Det här verktyget har inga konfigurerbara inställningar." -operations_one = "Åtgärd ({{count}})" -operations_other = "Åtgärder ({{count}})" output = "Utdata" -removeStep = "Ta bort åtgärd" save = "Spara ändringar" scheduleEvery = "Kör varje" -sources = "Källor" -sourcesLoading = "Läser in källor..." trigger = "Trigger" triggerManual = "Endast manuellt" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "dagar" hours = "timmar" minutes = "minuter" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Ta bort \"{{name}}\"? Detta kan inte ångras." cancel = "Avbryt" @@ -7867,6 +7825,37 @@ connectSource = "Anslut en källa" description = "Skapa din första pipeline: välj källorna den körs över, kedja åtgärderna och välj vart utdata ska skickas." title = "Inga pipelines ännu" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktiva" paused = "Pausade" @@ -7879,7 +7868,6 @@ completed_one = "Körning slutförd." completed_other = "Alla {{count}} körningar slutförda." empty = "Inget att köra: källorna hade inga dokument att bearbeta." failed = "Körning misslyckades: {{error}}" -historyCleared = "Historiken har rensats. Nästa körning bearbetar om allt som för närvarande finns i källorna." inFlight = "Inget nytt att köra: dokument bearbetas fortfarande från en tidigare körning." parked_one = "Inget att köra: {{count}} dokument misslyckades tidigare och är parkerat. Åtgärda orsaken och rensa sedan historiken för att försöka igen." parked_other = "Inget att köra: {{count}} dokument misslyckades tidigare och är parkerade. Åtgärda orsaken och rensa sedan historiken för att försöka igen." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Källor" status = "Status" steps = "Steg" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Mappbevakning" @@ -8209,6 +8198,24 @@ label = "Bifoga till ett Jira-ärende" description = "Skickar det bearbetade dokumentet som en bilaga." label = "Skicka dokumentet via e-post (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Skriver det bearbetade dokumentet till en mapp." label = "Ladda upp till Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Maximalt utnyttjad" subtitle = "Din kostnadsfria testperiod pågår till {{date}}. Inget kort krävs." title = "Enterprise-testperiod" -[portal.search] -ariaLabel = "Sök" -placeholder = "Sök i Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Snabbåtgärder visas här när de är tillgängliga." -noActionsTitle = "Inga snabbåtgärder" -noMatches = "Inga träffar för \"{{query}}\"" -noMatchesDescription = "Prova ett annat sökord eller bläddra i katalogen." - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "Avbryt" confirm = "Ta bort" title = "Ta bort källa?" -[portal.sources.empty] -description = "Anslut en mapp (och snart molnlagring) så att dina policyer har någonstans att hämta dokument från." -title = "Inga källor anslutna än" - [portal.sources.kpi] inUse = "Används" total = "Anslutningar" @@ -8785,6 +8778,7 @@ unused = "Oanvänd" documents = "Dokument" source = "Källa" status = "Status" +type = "Type" usedBy = "Policyer" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Policyer och integritet" [settings.preferences] title = "Preferenser" -[settings.search] -placeholder = "Sök i inställningar..." - [settings.security] description = "Uppdatera ditt lösenord för att hålla ditt konto säkert." title = "Säkerhet" @@ -10128,6 +10119,7 @@ last = "Sista sidan" maintainRatio = "Växla bibehållet bildförhållande" next = "Nästa sida" noSavedSigs = "Inga sparade signaturer hittades" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Personliga signaturer" previous = "Föregående sida" redo = "Gör om" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Borttagning av bakgrund misslyckades" hint = "Ladda upp en PNG- eller JPG-bild av din signatur" label = "Ladda upp signaturbild" placeholder = "Välj bildfil" +previewAlt = "Current image signature" processing = "Bearbetar bild..." removeBackground = "Ta bort vit bakgrund (gör transparent)" @@ -10179,6 +10172,17 @@ saved = "Välj en sparad signatur ovan, klicka sedan var som helst i PDF:en för text = "När du har angett ditt namn ovan, klicka var som helst i PDF:en för att placera din signatur." title = "Så här lägger du till signatur" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Flytta signatur" pause = "Pausa placering" @@ -10625,6 +10629,23 @@ title = "Ladda upp till server" updateButton = "Uppdatera på servern" uploadButton = "Ladda upp till server" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Gå tillbaka" pdfTools = "PDF-verktyg" placeholder = "Välj ett verktyg för att komma igång" premiumFeature = "Premiumfunktion:" -searchTools = "Sök verktyg" toolsHeader = "Verktyg" viewAllTools = "Visa alla verktyg" @@ -10822,6 +10842,9 @@ removal = "Borttagning" signing = "Signering" verification = "Verifiering" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Inga verktyg hittades" noTools = "Inga verktyg tillgängliga" @@ -10979,6 +11002,13 @@ approver = "Godkänner policy" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Avbryt inbjudan till {{email}}? De kommer inte att kunna gå med via den aktuella länken." cancelInviteTitle = "Avbryt inbjudan" @@ -10998,10 +11028,8 @@ title = "Inga medlemmar än" addToTeam = "Lägg till i team" guestCount = "{{count}} gäst" guests = "Gäster" -guestsDesc = "Externa samarbetspartner, begränsade till det du har delat. Endast Editor." ledBy = "ledd av {{owner}}" org = "Organisation" -orgDesc = "Ägare med organisationsövergripande behörighet och policygodkännande" owners = "{{count}} ägare" team = "{{name}}-team" teamMeta = "{{count}} personer" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Inbjuden av {{who}}" cancel = "Avbryt" -count = "{{count}} väntande" -desc = "Inbjudna personer som inte har gått med än. De upptar en plats tills de accepterar." expiresInDays_one = "Upphör om {{count}} dag" expiresInDays_other = "Upphör om {{count}} dagar" expiresToday = "Upphör idag" title = "Väntande inbjudningar" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Något gick fel vid kontakt med backend, eller så har du inte åtkomst. Försök igen." title = "Kunde inte läsa in medlemmar" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Sök bilagor" title = "Bilagor" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Bokmärkestitel" bookmarkTitleRequired = "Bokmärkestitel krävs" closeSidebar = "Stäng sidopanel för bokmärken" collapseAll = "Fäll ihop alla bokmärken" +empty = "No bookmarks in this document" expandAll = "Expandera alla bokmärken" searchPlaceholder = "Sök bokmärken" +title = "Bookmarks" [viewer.comments] addComment = "Lägg till kommentar" @@ -11264,10 +11297,12 @@ locateAnnotation = "Hitta i dokumentet" moreActions = "Fler åtgärder" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Sida {{page}}" placingHint = "Klicka på en sida för att placera… (avbryt)" removeCommentOnly = "Ta bort endast kommentaren" saveReply = "Spara svar" +searchPlaceholder = "Search comments" title = "Kommentarer" typeComment = "Kommentar" typeInsertText = "Infoga text" @@ -11290,6 +11325,7 @@ unsavedDesc = "Du har osparade ändringar" closeSidebar = "Stäng sidopanel för lager" hideAll = "Dölj alla lager" showAll = "Visa alla lager" +title = "Layers" [viewer.link] delete = "Ta bort länk" @@ -11646,6 +11682,7 @@ exitRedaction = "Avsluta maskeringsläge" exportAll = "Exportera PDF" exportSelected = "Exportera valda sidor" formFill = "Fyll i formulär" +hideToolbar = "Hide toolbar" multiTool = "Multiverktyg" panMode = "Panoreringsläge" print = "Skriv ut PDF" @@ -11666,6 +11703,7 @@ selectAll = "Markera alla" selectByNumber = "Markera efter sidnummer" selectLanguage = "Välj språk" share = "Dela" +showToolbar = "Show toolbar" toggleAnnotations = "Visa/dölj anteckningar" toggleAttachments = "Visa/dölj bilagor" toggleBookmarks = "Visa/dölj bokmärken" diff --git a/frontend/editor/public/locales/th-TH/translation.toml b/frontend/editor/public/locales/th-TH/translation.toml index f50f5f2fa9..4d194d6e06 100644 --- a/frontend/editor/public/locales/th-TH/translation.toml +++ b/frontend/editor/public/locales/th-TH/translation.toml @@ -1,7 +1,6 @@ alphabet = "ตัวอักษร" apply = "ใช้" applyAndContinue = "บันทึกและออก" -areYouSure = "แน่ใจหรือไม่ว่าต้องการออก?" back = "ย้อนกลับ" black = "ดำ" blue = "น้ำเงิน" @@ -106,6 +105,7 @@ undoSuccess = "ยกเลิกการดำเนินการเรี unknown = "ไม่ทราบ" unpin = "เอาปักหมุดไฟล์ออก (แทนที่หลังรันเครื่องมือ)" unsavedChanges = "คุณมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึกใน PDF" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "การเปลี่ยนแปลงที่ยังไม่ได้บันทึก" unsupported = "ไม่รองรับ" username = "ชื่อผู้ใช้" @@ -1879,6 +1879,9 @@ width = "ความกว้าง" [app] description = "ทางเลือกแทน Adobe Acrobat ฟรี (ดาวน์โหลด 10M+)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "แปลงเป็น PDF/A-3b" convertToPdfA3bDescription = "สร้าง PDF สำหรับเก็บถาวรพร้อมฝังไฟล์แนบ" @@ -2933,12 +2936,16 @@ text = "เพื่อให้สิทธิ์เหล่านี้ไม [chat.actions] copy = "คัดลอกข้อความ" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "ตัวเลือกเอเจนต์ของ Stirling" +assistant = "AI Assistant" clearChat = "ล้างแชต" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "การดำเนินการนี้จะใช้เคร tooltip = "เลือกสีจากหน้าจอ" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "เลือกสี" [common] +actions = "Actions" back = "ย้อนกลับ" cancel = "ยกเลิก" close = "ปิด" +codeSample = "Code sample" collapse = "ยุบ" confirm = "ยืนยัน" continue = "ดำเนินการต่อ" copied = "คัดลอกแล้ว!" copy = "คัดลอก" done = "เสร็จสิ้น" +enterValue = "Enter value" error = "ข้อผิดพลาด" expand = "ขยาย" loading = "กำลังโหลด..." next = "ถัดไป" +open = "Open" preview = "แสดงตัวอย่าง" previous = "ก่อนหน้า" refresh = "รีเฟรช" remaining = "คงเหลือ" retry = "ลองใหม่" save = "บันทึก" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "ล้างที่เลือก" @@ -3185,6 +3199,7 @@ title = "วิธีการบีบอัด" [compress.settings] desiredSize = "ขนาดไฟล์ที่ต้องการ" desiredSizePlaceholder = "ป้อนขนาด" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "การบีบอัดเป็นวิธีง่าย ๆ ในการลดขนาดไฟล์ เลือก ขนาดไฟล์ เพื่อป้อนขนาดเป้าหมายและให้เราปรับคุณภาพให้คุณ เลือก คุณภาพ เพื่อกำหนดความแรงการบีบอัดด้วยตนเอง" @@ -3387,6 +3402,24 @@ pdfOptions = "ตัวเลือก PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "เลือกประเภทต้นทางก่อน" settings = "การตั้งค่า" single = "เดี่ยว" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "ค้นหา..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "แก้ไข" @@ -3824,11 +3861,13 @@ shareSelected = "แชร์ที่เลือก" sharing = "การแชร์" showAll = "แสดงทั้งหมด" showHistory = "แสดงประวัติ" +sortBy = "Sort files" sortByDate = "เรียงตามวันที่" sortByName = "เรียงตามชื่อ" sortBySize = "เรียงตามขนาด" storage = "ที่เก็บ" storageState = "ที่เก็บข้อมูล" +storageUsed = "Storage used" synced = "ซิงค์แล้ว" title = "อัปโหลดไฟล์ PDF" toolChain = "เครื่องมือที่ใช้" @@ -3874,8 +3913,12 @@ addFiles = "เพิ่มไฟล์" addingFiles = "กำลังเพิ่มไฟล์…" collapse = "ยุบแถบข้าง" customizeGroups = "ปรับแต่งกลุ่ม" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "เปิดไฟล์เพื่อเริ่มต้น" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "ขยายแถบข้าง" googleDrive = "Google Drive" googleDriveDisabled = "ยังไม่ได้กำหนดค่า Google Drive" @@ -3888,15 +3931,17 @@ openFromComputer = "เปิดจากคอมพิวเตอร์" openSettings = "เปิดการตั้งค่า" other = "อื่นๆ" recent = "ล่าสุด" -search = "ค้นหา" -searchPlaceholder = "ค้นหาไฟล์..." viewAll = "ดูไฟล์ทั้งหมด {{count}} ไฟล์" [fileSidebar.fileItem] closeViewer = "ปิดตัวแสดงผล" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "เปิดในตัวแสดงผล" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "รีเซ็ตเป็นค่าเริ่มต้น" subtitle = "จัดกลุ่มไฟล์ของคุณเป็นหมวดหมู่หลัก เพิ่มป้ายกำกับที่มีอยู่หรือป้ายกำกับใหม่ไปยังหมวดหมู่ เปลี่ยนชื่อ หรือสร้างของคุณเอง ไฟล์ที่ไม่อยู่ในหมวดหมู่ใดของคุณจะแสดงใต้ “อื่นๆ”" title = "หมวดหมู่แถบด้านข้าง" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "เพิ่มไปยังพื้นที่ทำงาน" addToWorkspaceCount = "เพิ่ม {{count}} ไปยังพื้นที่ทำงาน" @@ -3918,7 +3971,6 @@ backToMyFiles = "ย้อนกลับไปยังไฟล์ของฉ breadcrumbs = "เส้นทางโฟลเดอร์" cancel = "ยกเลิก" classification = "การจัดประเภท" -clearSearch = "ล้างการค้นหา" clearSelection = "ล้างการเลือก" closeDetails = "ปิดรายละเอียด" create = "สร้าง" @@ -3950,6 +4002,7 @@ downloadAll = "ดาวน์โหลดทั้งหมด" downloadVersion = "ดาวน์โหลดเวอร์ชันนี้" dropOverlay = "ปล่อยไฟล์เพื่ออัปโหลด" dropOverlaySub = "ไฟล์จะเริ่มใน ในเครื่อง ใช้ 'ย้ายไปยัง' หรือ 'บันทึกไปยังคลาวด์' เพื่อจัดระเบียบลงในโฟลเดอร์." +duplicate = "Duplicate" file = "ไฟล์" fileInfo = "File info" fileMenu = "การดำเนินการไฟล์" @@ -3983,8 +4036,6 @@ resizeFolderTree = "ปรับขนาดต้นไม้โฟลเดอ save = "บันทึก" saveToServer = "บันทึกไปยังเซิร์ฟเวอร์" saveToServerDisabledHint = "การบันทึกไปยังเซิร์ฟเวอร์ไม่ได้เปิดใช้งานบนเซิร์ฟเวอร์นี้ โปรดขอให้ผู้ดูแลระบบเปิดใช้งาน." -search = "ค้นหา" -searchPlaceholder = "ค้นหาโฟลเดอร์นี้และโฟลเดอร์ย่อย" selectAll = "เลือกทั้งหมด" selectAllHint = "คลิกเพื่อเลือกทั้งหมด เคล็ดลับ: กด Ctrl (หรือ Cmd) เพื่อเพิ่มทีละไฟล์ กด Shift เพื่อเลือกช่วง." selectedCount = "เลือกแล้ว {{count}}" @@ -4044,6 +4095,10 @@ title = "ยังไม่มีไฟล์คลาวด์" hint = "ไฟล์ที่บันทึกโดยไม่อัปโหลดจะอยู่ที่นี่ ปล่อยไฟล์เพื่อเพิ่ม." title = "ไม่มีไฟล์เฉพาะในเครื่อง" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "ไฟล์ที่คุณเปิดหรือแก้ไขจะปรากฏที่นี่." title = "ยังไม่มีการแก้ไข" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "ไม่สามารถลบโฟลเดอร์ได้." deleteFolderFailedDetail = "ไม่สามารถลบโฟลเดอร์ได้: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "ไม่สามารถอัปเดตลักษณะที่ปรากฏของโฟลเดอร์ได้." folderAppearanceFailedDetail = "ไม่สามารถอัปเดตลักษณะที่ปรากฏของโฟลเดอร์ได้: {{message}}" moveFilesFailed = "ไม่สามารถย้ายไฟล์ได้." @@ -4113,6 +4170,11 @@ localHint = "เก็บไว้เฉพาะในเบราว์เซ shared = "แชร์" sharedHint = "แชร์กับคุณผ่านลิงก์" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "เก่าสุดก่อน" modifiedDesc = "ล่าสุดก่อน" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "ไม่มีไฟล์ในที่เก็บ open = "เปิด" openFile = "เปิดไฟล์" openFiles = "เปิดไฟล์หลายไฟล์" +selectFile = "Select {{name}}" selectFromStorage = "เลือกจากที่เก็บ" upload = "อัปโหลด" uploadFile = "อัปโหลดไฟล์" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "บันทึกทั้งหมดแล้ว" +analyzingFields = "Analysing form fields..." extractCsvError = "ดึง CSV ไม่สำเร็จ" extractXlsxError = "ดึง XLSX ไม่สำเร็จ" +filled = "filled" flattenAfterFilling = "ทำให้แบนหลังจากกรอก" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "จำเป็น" requiredFieldsError = "โปรดกรอกช่องที่จำเป็นทั้งหมด" rescanFields = "สแกนฟิลด์อีกครั้ง" @@ -4795,9 +4863,6 @@ title = "รูปภาพเป็น PDF" [imageToPdf] tags = "การแปลง, รูปภาพ, JPG, ภาพ, รูปถ่าย" -[infoBanner] -dismiss = "ปิด" - [invite] acceptError = "ไม่สามารถสร้างบัญชี" accountFor = "กำลังสร้างบัญชีสำหรับ" @@ -4979,6 +5044,34 @@ uploadSuccess = "อัปโหลดสำเร็จ!" uploadSuccessMessage = "ถ่ายโอนรูปภาพของคุณเรียบร้อยแล้ว" validating = "กำลังตรวจสอบเซสชัน..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "สแกนเพื่ออัปโหลดรูปภาพ รูปภาพจะแปลงเป็น PDF อัตโนมัติ" descriptionNoConvert = "สแกนเพื่ออัปโหลดรูปภาพจากอุปกรณ์มือถือของคุณ" @@ -4997,8 +5090,13 @@ title = "อัปโหลดจากมือถือ" tags = "เครื่องมือหลายตัว, หลายการดำเนินการ, UI, คลิกและลาก, ส่วนหน้า, ฝั่งไคลเอนต์, อินเตอร์แอคทีฟ, เคลื่อนย้าย" title = "เครื่องมือ PDF หลายตัว" -[navbar] -search = "ค้นหา" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "การยืนยันตัวตนไม่สำเร็จ คุณสามารถปิดหน้าต่างนี้และลองอีกครั้ง" @@ -5127,6 +5225,7 @@ activeFiles = "มุมมอง ไฟล์ที่ใช้งา allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "ปิด" cropSettings = "เมื่อเราเลือกไฟล์ที่ต้องการครอบตัดแล้ว เราสามารถกำหนดค่าเครื่องมือครอบตัดเพื่อเลือกพื้นที่ที่ต้องการครอบตัด PDF" +dialogLabel = "Onboarding" fileCheckbox = "การคลิกที่หนึ่งในไฟล์จะเลือกไฟล์นั้นเพื่อประมวลผล คุณสามารถเลือกหลายไฟล์เพื่อทำแบบชุดได้" fileReplacement = "ไฟล์ที่แก้ไขจะมาแทนที่ไฟล์เดิมใน Workbench โดยอัตโนมัติ ช่วยให้คุณรันเครื่องมืออื่นต่อได้ง่าย" filesButton = "ปุ่ม ไฟล์ ในแถบเข้าถึงด่วนช่วยให้คุณอัปโหลด PDF เพื่อใช้เครื่องมือต่างๆ ได้" @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF เป็น PDF/A" tags = "การจัดเก็บ, ระยะยาว, มาตรฐาน, การแปลง, การเก็บรักษา" title = "PDF เป็น PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "พิมพ์,มาตรฐาน,แปลง,การผลิต,ก่อนพิมพ์,เก็บถาวร" title = "แปลง PDF เป็น PDF/X" @@ -6045,6 +6151,7 @@ small = "500 เครดิต" xsmall = "100 เครดิต" [plan.availablePlans] +currency = "Billing currency" subtitle = "เลือกแผนที่เหมาะกับความต้องการของคุณ" title = "แผนที่มีให้" @@ -6624,6 +6731,7 @@ reachedTitle = "ถึงขีดจำกัดค่าใช้จ่าย title = "ไม่สามารถเปิดพอร์ทัล Stripe ได้" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "จาก {{allowance}} PDFs ฟรีถูกใช้แล้ว" capSuffix_other = "จาก {{allowance}} PDFs ฟรีถูกใช้แล้ว" eyebrow = "ทดลองใช้ Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "ส่งอีเมลเอกสารหรือการแจ้งเตือนเมื่อ policy ทำงาน" label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "จัดเก็บเอกสารที่ประมวลผลแล้วลงใน Nextcloud" @@ -7077,11 +7211,6 @@ title = "SDKs อย่างเป็นทางการ" beta = "Beta" deprecated = "เลิกใช้แล้ว" -[portal.docs.search] -empty = "ไม่มีเอกสารที่ตรงกัน" -placeholder = "ค้นหาเอกสาร" -results = "{{count}} ผลลัพธ์" - [portal.docs.skills] eyebrow = "SKILLS" lead = "ความสามารถที่รวมมาและตั้งชื่อไว้ ซึ่งเอเจนต์ของคุณเรียกใช้เป็นเครื่องมือเดียว แต่ละ skill เป็นสายการดำเนินการแบบกำหนดแน่นอนพร้อมการประเมินแนบอยู่" @@ -7180,11 +7309,11 @@ editorAction = "Editor" empty = "ไม่มีเอกสารที่ตรงกับตัวกรองนี้" rowActions = "การดำเนินการของแถว" sensitiveLabel = "ละเอียดอ่อน" -sensitiveTitle = "ละเอียดอ่อน — ต้องมีสิทธิ์เข้าถึง" [portal.documents.table.columns] action = "Pipeline / การดำเนินการ" document = "เอกสาร" +labels = "Labels" product = "ผลิตภัณฑ์" status = "สถานะ" time = "เวลา" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "เห็นล่าสุด" region = "ภูมิภาค" status = "สถานะ" +target = "Target" version = "เวอร์ชัน" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "มุมมองนี้พบข้อผิดพลาด retry = "ลองอีกครั้ง" title = "มีบางอย่างผิดพลาดในหน้านี้" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "กลับ" body = "ดาวน์โหลดแอป desktop หรือ self-host server" @@ -7380,11 +7560,6 @@ cancel = "ยกเลิก" confirm = "เพิกถอนคีย์" title = "เพิกถอน API key" -[portal.infrastructure.attestationLabel] -attested = "รับรองแล้ว" -inScope = "อยู่ในขอบเขต" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "กรองเหตุการณ์ตรวจสอบตามหมวดหมู่" heading = "Audit logs" @@ -7456,11 +7631,6 @@ info = "ข้อมูล" success = "สำเร็จ" warning = "คำเตือน" -[portal.infrastructure.certLabel] -certified = "ผ่านการรับรอง" -inProgress = "กำลังดำเนินการ" -notStarted = "ยังไม่เริ่ม" - [portal.infrastructure.createKey] cancel = "ยกเลิก" createKey = "สร้างคีย์" @@ -7474,238 +7644,10 @@ subtitleCreated = "คัดลอก secret นี้ตอนนี้ — จ title = "สร้าง API key" titleCreated = "สร้างคีย์แล้ว" -[portal.infrastructure.deployLabel] -live = "ใช้งานจริง" -queued = "เข้าคิวแล้ว" -rolledBack = "ย้อนกลับแล้ว" -rolling = "กำลังเผยแพร่" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/นาที" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "ปรับใช้โดย" -environment = "Environment" -product = "ผลิตภัณฑ์" -status = "สถานะ" -version = "เวอร์ชัน" -when = "เมื่อใด" - -[portal.infrastructure.deployments.recent] -heading = "การปรับใช้ล่าสุด" -subheading = "การ rollout ล่าสุดในผลิตภัณฑ์และ environments" - -[portal.infrastructure.deployments.regionColumns] -instances = "อินสแตนซ์" -latency = "เวลาแฝง" -load = "โหลด" -p99 = "P99" -region = "ภูมิภาค" -status = "สถานะ" -throughput = "Throughput" -uptime = "Uptime" -version = "เวอร์ชัน" - -[portal.infrastructure.deployments.regions] -heading = "ภูมิภาค" -subheading = "สุขภาพแบบสดสำหรับทุกภูมิภาค Stirling ที่ปรับใช้ — เวลาแฝง โหลด และเวอร์ชัน rollout" - -[portal.infrastructure.deployments.regions.empty] -description = "ภูมิภาคที่ปรับใช้จะแสดงที่นี่เมื่อ workspace ของคุณได้รับการจัดสรรแล้ว" -title = "ยังไม่มีภูมิภาคที่ปรับใช้" - [portal.infrastructure.keyLabel] active = "ใช้งานอยู่" revoked = "ถูกเพิกถอน" -[portal.infrastructure.modelLabel] -active = "ใช้งานอยู่" -degraded = "เสื่อมประสิทธิภาพ" -disabled = "ปิดใช้งาน" - -[portal.infrastructure.models] -heading = "Models" -msValue = "{{value}} ms" -subheading = "แค็ตตาล็อก model และ routing ที่ขับเคลื่อนการประมวลผลเอกสารทั่ว workspace ของคุณ" - -[portal.infrastructure.models.byom] -description = "ลงทะเบียน model แบบ on-prem หรือโฮสต์เอง และปักหมุดกับภูมิภาคสำหรับการประมวลผลที่ผูกกับถิ่นที่อยู่ของข้อมูล" -title = "นำ model ของคุณมาเอง" - -[portal.infrastructure.models.catalogue] -heading = "แค็ตตาล็อก" -sub = "Managed models ที่พร้อมใช้งานสำหรับ workspace ของคุณ พร้อมเวลาแฝงและค่าใช้จ่ายแบบสด" -subEnterprise = "Managed, bring-your-own และ on-prem models — พร้อมการปักหมุดรายภูมิภาค" - -[portal.infrastructure.models.catalogue.empty] -description = "Models ในแค็ตตาล็อกของ workspace ของคุณจะแสดงที่นี่" -title = "ไม่มี models ที่พร้อมใช้งาน" - -[portal.infrastructure.models.columns] -cost = "ค่าใช้จ่าย" -latency = "เวลาแฝง" -load = "โหลด" -model = "Model" -status = "สถานะ" -type = "ประเภท" -version = "เวอร์ชัน" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/ครั้ง" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Models ที่ใช้งานอยู่" -avgLatency = "เวลาแฝงเฉลี่ย" -included = "รวมแล้ว" -monthlySpend = "ค่าใช้จ่าย model รายเดือน" - -[portal.infrastructure.models.routing] -empty = "ยังไม่ได้กำหนดค่ากฎ routing" -heading = "กฎ routing" -sub = "Model ใดจัดการแต่ละการดำเนินการ ค่าเริ่มต้นจะใช้เมื่อไม่มีกฎที่แคบกว่าตรงกัน" -subLocked = "Route การดำเนินการไปยัง models เฉพาะ — พร้อมใช้งานในแผนแบบชำระเงิน" - -[portal.infrastructure.models.routing.lockedBanner] -description = "อัปเกรดเป็น Pro เพื่อควบคุมว่า model ใดจัดการแต่ละการดำเนินการและประเภทเอกสาร" -title = "Model routing เป็นฟีเจอร์แบบชำระเงิน" - -[portal.infrastructure.models.routingColumns] -default = "ค่าเริ่มต้น" -docType = "ประเภทเอกสาร" -modelForAria = "Model สำหรับ {{operation}}" -operation = "การดำเนินการ" -routedTo = "Route ไปยัง" - -[portal.infrastructure.modelTypeLabel] -classification = "การจัดประเภท" -extraction = "การดึงข้อมูล" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "เสื่อมประสิทธิภาพ" -down = "ล่ม" -healthy = "ปกติ" - -[portal.infrastructure.security.access.byok] -description = "ระบุคีย์จาก KMS ของคุณเอง Stirling เข้ารหัสด้วยคีย์นั้นแต่ยังอ่านได้" -label = "Bring your own key (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "คีย์ไม่ออกจาก KMS ของคุณ Stirling ถือเฉพาะ ciphertext" -label = "Hold your own key (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling จัดการคีย์เข้ารหัส ง่ายที่สุด — ไม่ต้องทำ key ops ฝั่งคุณ" -label = "คีย์ที่ Stirling ถือไว้" - -[portal.infrastructure.security.accessPolicy] -heading = "นโยบายการเข้าถึงเอกสาร" -subheading = "ควบคุมว่าใครสามารถถอดรหัสเอกสารที่ประมวลผลแล้วเมื่อจัดเก็บอยู่ได้" - -[portal.infrastructure.security.attestations] -heading = "การรับรองการปฏิบัติตามข้อกำหนด" -noReport = "ไม่มีรายงาน" -subheading = "สถานะการตรวจสอบตามแต่ละ framework พร้อมรายงานสำหรับ controls ที่ได้รับการรับรอง" -viewReport = "ดูรายงาน →" - -[portal.infrastructure.security.compliance] -heading = "การปฏิบัติตามข้อกำหนด" -subheading = "การรับรองและใบรับรองที่ครอบคลุมแพลตฟอร์ม Stirling" - -[portal.infrastructure.security.empty] -description = "การกำหนดค่าความปลอดภัยของ workspace ของคุณจะแสดงที่นี่" -title = "ไม่พร้อมใช้งานสถานะความปลอดภัย" - -[portal.infrastructure.security.hyokBanner] -description = "ด้วย HYOK คีย์เข้ารหัสจะไม่ออกจาก KMS ของคุณ Stirling จัดเก็บและประมวลผลเฉพาะ ciphertext ที่คุณสามารถเพิกถอนได้ทุกเมื่อ" -title = "Stirling ไม่สามารถถอดรหัสเอกสารของคุณได้" - -[portal.infrastructure.security.ipAllowlist] -empty = "ยังไม่ได้กำหนดค่าช่วง IP — อนุญาตทุก IP" -heading = "IP allowlist" -sub = "การเข้าถึง API ถูกจำกัดไว้ที่ช่วง CIDR เหล่านี้" -subLocked = "จำกัดการเข้าถึง API ให้กับช่วง IP ที่รู้จัก — พร้อมใช้งานในแผนแบบชำระเงิน" - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "อัปเกรดเป็น Pro เพื่อจำกัดการเข้าถึง API ให้กับเครือข่ายเฉพาะ" -title = "IP allowlisting เป็นฟีเจอร์แบบชำระเงิน" - -[portal.infrastructure.security.ipColumns] -added = "เพิ่มเมื่อ" -addedBy = "เพิ่มโดย" -cidr = "CIDR" -label = "ป้ายกำกับ" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "การจัดการคีย์เข้ารหัส" -keyId = "ตัวระบุคีย์" -lastRotated = "หมุนเวียนล่าสุด" -rotateKey = "หมุนเวียนคีย์" -rotationPolicy = "นโยบายการหมุนเวียน" -subheading = "การดูแลคีย์ที่เข้ารหัสเอกสารเมื่อจัดเก็บอยู่ — ใครสามารถถอดรหัสได้ และคีย์หมุนเวียนอย่างไร" - -[portal.infrastructure.security.managedBanner] -description = "การดูแลคีย์แบบ bring-your-own-key (BYOK) และ hold-your-own-key (HYOK) พร้อมใช้งานใน Enterprise อัปเกรดเพื่อระบุคีย์จาก KMS ของคุณเอง" -title = "คีย์ถูกจัดการโดย Stirling ในแผนของคุณ" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "เอเชียแปซิฟิก" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR data boundary" -label = "สหภาพยุโรป" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "สหรัฐอเมริกา" - -[portal.infrastructure.security.residencyHeader] -heading = "ถิ่นที่อยู่ของข้อมูล" -subheading = "ตำแหน่งที่เอกสารถูกจัดเก็บและประมวลผล" - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "ใช้แล้ว {{value}}" - -[portal.infrastructure.storage.empty] -description = "พื้นที่จัดเก็บที่เชื่อมต่อและการใช้งานจะแสดงที่นี่" -title = "ยังไม่ได้กำหนดค่าพื้นที่จัดเก็บ" - -[portal.infrastructure.storage.lifecycle] -active = "ใช้งานอยู่" -activeRange = "0–{{value}}d" -archived = "เก็บถาวรแล้ว" -coldStorage = "cold storage" -deleted = "ลบแล้ว" -never = "ไม่เคย" -purged = "ล้างแล้ว" - -[portal.infrastructure.storage.providers] -connect = "เชื่อมต่อ" -connected = "เชื่อมต่อแล้ว" -heading = "ผู้ให้บริการที่เชื่อมต่อ" -subheading = "ตำแหน่งที่เขียน artifacts ที่ประมวลผลแล้ว" - -[portal.infrastructure.storage.retention] -heading = "การเก็บรักษา" -subheading = "ระยะเวลาที่เก็บ artifacts ก่อนการลบตาม lifecycle" -windowLabel = "หน้าต่างการเก็บรักษาเริ่มต้น" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} วัน" -days_other = "{{count}} วัน" -never = "ไม่ลบ" - -[portal.infrastructure.storage.totalUsage] -heading = "การใช้งานรวม" -progressLabel = "พื้นที่จัดเก็บที่ใช้" -subheading = "พื้นที่จัดเก็บที่ใช้รวมทุกผู้ให้บริการที่เชื่อมต่อ" - [portal.infrastructure.tabs] apiKeys = "API Keys" audit = "Audit Logs" @@ -7715,11 +7657,9 @@ security = "ความปลอดภัย" storage = "พื้นที่จัดเก็บ" [portal.integrations] -addAnother = "เพิ่มอีกหนึ่ง" availableHeading = "พร้อมใช้งาน" comingSoonHeading = "เร็ว ๆ นี้" connect = "เชื่อมต่อ" -connectedHeading = "เชื่อมต่อแล้ว" connectionCount_one = "การเชื่อมต่อ {{count}} รายการ" connectionCount_other = "การเชื่อมต่อ {{count}} รายการ" customApi = "API แบบกำหนดเอง" @@ -7737,6 +7677,10 @@ security = "ความปลอดภัย" signing = "การลงนาม" storage = "พื้นที่จัดเก็บ" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "เชื่อมต่อแล้ว" @@ -7767,7 +7711,6 @@ integrations = "Integrations" pipelines = "Pipelines" policies = "นโยบาย" procurement = "การจัดซื้อ" -settings = "การตั้งค่า" sources = "แหล่งที่มา" usage = "การใช้งานและการเรียกเก็บเงิน" users = "ผู้ใช้" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Pipeline ใหม่" [portal.pipelines.builder] -addStep = "เพิ่มเครื่องมือ" +activate = "Activate" back = "กลับไปที่ pipelines" cannotFollow = "รับ {{produced}} ไม่ได้" chooseAccount = "เลือกบัญชี" @@ -7792,28 +7735,38 @@ chooseDestination = "เลือกปลายทาง" chooseOperation = "เลือกสิ่งที่ขั้นตอนนี้ทำ" chooseSource = "เลือกแหล่งที่มา" discard = "ละทิ้งการเปลี่ยนแปลง" -enabled = "เปิดใช้งาน" inputs = "อินพุต" inputSource = "แหล่งที่มาของอินพุต" inputTrigger = "Trigger" keepEditing = "แก้ไขต่อ" +moreActions = "More actions" needsConfiguring = "ต้องตั้งค่า" -needsUpload = "ต้องมีไฟล์ที่อัปโหลด" -noSources = "ยังไม่มีแหล่งที่มาเชื่อมต่อ สร้างแหล่งที่มาเพื่อใช้เป็นอินพุต" +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "ไม่มีเครื่องมือที่ตรงกับการค้นหาของคุณ" -pipelineSettings = "การตั้งค่า pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "ค้นหาเครื่องมือ" -selectToolBody = "เพิ่มเครื่องมือเพื่อสร้าง pipeline ของคุณ" -selectToolTitle = "ยังไม่มีเครื่องมือ" sendToSystem = "ส่งไปยังระบบอื่น" stepsIncompatible = "ขั้นตอนเหล่านี้ไม่สามารถทำงานกับผลลัพธ์จากขั้นตอนก่อนหน้าได้: {{tools}}" stepsNeedSetup = "ขั้นตอนเหล่านี้ยังต้องตั้งค่าก่อนบันทึก: {{tools}}" -toolSettings = "การตั้งค่าเครื่องมือ" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "การดำเนินการที่ไม่รู้จัก เก็บไว้ตามเดิม" unsavedBody = "คุณมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึก บันทึกก่อนออก หรือจะละทิ้ง?" unsavedTitle = "การเปลี่ยนแปลงที่ยังไม่ได้บันทึก" -uploadUnsupported = "ไฟล์ที่อัปโหลดยังไม่รองรับใน pipelines ดังนั้นขั้นตอนเหล่านี้จึงไม่สามารถบันทึกได้: {{tools}}" usesDefaults = "ทำงานด้วยการตั้งค่าเริ่มต้น" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "รวมทุกไฟล์จากขั้นตอนก่อนหน้า" @@ -7825,31 +7778,36 @@ undeclared-operation = "ตรวจสอบไม่ได้ว่าขั [portal.pipelines.composer] addTool = "เพิ่มเครื่องมือ" -cancel = "ยกเลิก" -chainEmpty = "เพิ่มเครื่องมือเพื่อเริ่มสร้าง pipeline ของคุณ" create = "สร้าง pipeline" +createPaused = "Create paused" editingUnsupported = "ยังไม่รองรับการแสดง params ของเครื่องมือเหล่านี้เพื่อแก้ไข" -moveDown = "ย้ายลง" -moveUp = "ย้ายขึ้น" +editSource = "Edit source" name = "ชื่อ" namePlaceholder = "เช่น Redaction sweep" noToolSettings = "เครื่องมือนี้ไม่มีการตั้งค่าที่กำหนดค่าได้" -operations_one = "การดำเนินการ ({{count}})" -operations_other = "การดำเนินการ ({{count}})" output = "ผลลัพธ์" -removeStep = "ลบการดำเนินการ" save = "บันทึกการเปลี่ยนแปลง" scheduleEvery = "รันทุก" -sources = "แหล่งที่มา" -sourcesLoading = "กำลังโหลดแหล่งที่มา..." trigger = "Trigger" triggerManual = "Manual เท่านั้น" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "วัน" hours = "ชั่วโมง" minutes = "นาที" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "ลบ \"{{name}}\" หรือไม่? การดำเนินการนี้ย้อนกลับไม่ได้" cancel = "ยกเลิก" @@ -7867,6 +7825,37 @@ connectSource = "เชื่อมต่อแหล่งที่มา" description = "สร้าง pipeline แรกของคุณ: เลือกแหล่งที่มาที่จะรัน เชื่อมโยงการดำเนินการ และเลือกตำแหน่งผลลัพธ์" title = "ยังไม่มี pipelines" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "ใช้งานอยู่" paused = "หยุดชั่วคราว" @@ -7879,7 +7868,6 @@ completed_one = "การรันเสร็จสมบูรณ์" completed_other = "การรันทั้งหมด {{count}} รายการเสร็จสมบูรณ์" empty = "ไม่มีอะไรให้รัน: แหล่งที่มาไม่มีเอกสารให้ประมวลผล" failed = "การรันล้มเหลว: {{error}}" -historyCleared = "ล้างประวัติแล้ว การรันครั้งถัดไปจะประมวลผลทุกอย่างที่อยู่ในแหล่งที่มาอีกครั้ง" inFlight = "ไม่มีรายการใหม่ให้รัน: เอกสารยังคงกำลังถูกประมวลผลจากการรันก่อนหน้า" parked_one = "ไม่มีอะไรให้รัน: เอกสาร {{count}} รายการล้มเหลวก่อนหน้านี้และถูกพักไว้ แก้ไขสาเหตุ จากนั้นล้างประวัติเพื่อลองใหม่" parked_other = "ไม่มีอะไรให้รัน: เอกสาร {{count}} รายการล้มเหลวก่อนหน้านี้และถูกพักไว้ แก้ไขสาเหตุ จากนั้นล้างประวัติเพื่อลองใหม่" @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "แหล่งที่มา" status = "สถานะ" steps = "ขั้นตอน" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "เฝ้าดูโฟลเดอร์" @@ -8209,6 +8198,24 @@ label = "แนบไปยัง Jira issue" description = "ส่งเอกสารที่ประมวลผลแล้วเป็นไฟล์แนบ" label = "ส่งเอกสารทางอีเมล (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "เขียนเอกสารที่ประมวลผลแล้วลงในโฟลเดอร์" label = "อัปโหลดไปยัง Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "ถึงขีดจำกัดแล้ว" subtitle = "trial ฟรีของคุณใช้งานได้ถึง {{date}} ไม่ต้องใช้บัตร" title = "Enterprise trial" -[portal.search] -ariaLabel = "ค้นหา" -placeholder = "ค้นหา Stirling — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "การดำเนินการด่วนจะปรากฏที่นี่เมื่อพร้อมใช้งาน" -noActionsTitle = "ไม่มีการดำเนินการด่วน" -noMatches = "ไม่พบรายการที่ตรงกับ \"{{query}}\"" -noMatchesDescription = "ลองใช้คำค้นอื่นหรือเรียกดู catalogue" - [portal.settings.groups] admin = "Admin" @@ -8743,10 +8740,6 @@ cancel = "ยกเลิก" confirm = "ลบ" title = "ลบแหล่งที่มาหรือไม่?" -[portal.sources.empty] -description = "เชื่อมต่อโฟลเดอร์ (และเร็ว ๆ นี้ cloud storage) เพื่อให้นโยบายของคุณมีที่สำหรับดึงเอกสาร" -title = "ยังไม่มีแหล่งที่มาที่เชื่อมต่อ" - [portal.sources.kpi] inUse = "กำลังใช้งาน" total = "การเชื่อมต่อ" @@ -8785,6 +8778,7 @@ unused = "ไม่ได้ใช้งาน" documents = "เอกสาร" source = "แหล่งที่มา" status = "สถานะ" +type = "Type" usedBy = "นโยบาย" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "นโยบายและความเป็นส่วนตั [settings.preferences] title = "ค่ากำหนด" -[settings.search] -placeholder = "ค้นหาหน้าการตั้งค่า..." - [settings.security] description = "อัปเดตรหัสผ่านของคุณเพื่อให้บัญชีปลอดภัย" title = "ความปลอดภัย" @@ -10128,6 +10119,7 @@ last = "หน้าสุดท้าย" maintainRatio = "สลับคงอัตราส่วนภาพ" next = "หน้าถัดไป" noSavedSigs = "ไม่พบลายเซ็นที่บันทึกไว้" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "ลายเซ็นส่วนตัว" previous = "หน้าก่อนหน้า" redo = "ทำซ้ำ" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "ลบพื้นหลังไม่สำเ hint = "อัปโหลดภาพลายเซ็นเป็น PNG หรือ JPG" label = "อัปโหลดภาพลายเซ็น" placeholder = "เลือกไฟล์รูปภาพ" +previewAlt = "Current image signature" processing = "กำลังประมวลผลภาพ..." removeBackground = "ลบพื้นหลังสีขาว (ทำให้โปร่งใส)" @@ -10179,6 +10172,17 @@ saved = "เลือกหนึ่งลายเซ็นที่บัน text = "หลังจากป้อนชื่อของคุณด้านบนแล้ว ให้คลิกที่ใดก็ได้บน PDF เพื่อวางลายเซ็น" title = "วิธีเพิ่มลายเซ็น" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "ย้ายลายเซ็น" pause = "หยุดการวาง" @@ -10625,6 +10629,23 @@ title = "อัปโหลดไปยังเซิร์ฟเวอร์" updateButton = "อัปเดตบนเซิร์ฟเวอร์" uploadButton = "อัปโหลดไปยังเซิร์ฟเวอร์" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "ย้อนกลับ" pdfTools = "เครื่องมือ PDF" placeholder = "เลือกเครื่องมือเพื่อเริ่มต้น" premiumFeature = "ฟีเจอร์พรีเมียม:" -searchTools = "ค้นหาเครื่องมือ" toolsHeader = "เครื่องมือ" viewAllTools = "ดูเครื่องมือทั้งหมด" @@ -10822,6 +10842,9 @@ removal = "การลบ" signing = "การลงนาม" verification = "การตรวจสอบ" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "ไม่พบเครื่องมือ" noTools = "ไม่มีเครื่องมือให้ใช้" @@ -10979,6 +11002,13 @@ approver = "อนุมัตินโยบาย" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "ยกเลิกคำเชิญไปยัง {{email}}? พวกเขาจะไม่สามารถเข้าร่วมด้วยลิงก์ปัจจุบันได้" cancelInviteTitle = "ยกเลิกคำเชิญ" @@ -10998,10 +11028,8 @@ title = "ยังไม่มีสมาชิก" addToTeam = "เพิ่มเข้าทีม" guestCount = "{{count}} guest" guests = "Guests" -guestsDesc = "ผู้ร่วมงานภายนอก จำกัดตามสิ่งที่คุณแชร์ Editor เท่านั้น" ledBy = "นำโดย {{owner}}" org = "องค์กร" -orgDesc = "เจ้าของที่มีอำนาจทั่วทั้งองค์กรและการอนุมัตินโยบาย" owners = "เจ้าของ {{count}} คน" team = "ทีม {{name}}" teamMeta = "{{count}} คน" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "เชิญโดย {{who}}" cancel = "ยกเลิก" -count = "รอดำเนินการ {{count}} รายการ" -desc = "ผู้ที่ได้รับเชิญแต่ยังไม่ได้เข้าร่วม พวกเขาจะถูกนับเป็นหนึ่งที่นั่งจนกว่าจะยอมรับ" expiresInDays_one = "หมดอายุใน {{count}} วัน" expiresInDays_other = "หมดอายุใน {{count}} วัน" expiresToday = "หมดอายุวันนี้" title = "คำเชิญที่รอดำเนินการ" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "เกิดข้อผิดพลาดในการติดต่อ backend หรือคุณไม่มีสิทธิ์เข้าถึง ลองอีกครั้ง" title = "ไม่สามารถโหลดสมาชิกได้" @@ -11237,12 +11267,15 @@ searchPlaceholder = "ค้นหาไฟล์แนบ" title = "ไฟล์แนบ" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "ชื่อบุ๊กมาร์ก" bookmarkTitleRequired = "จำเป็นต้องมีชื่อบุ๊กมาร์ก" closeSidebar = "ปิดแถบด้านข้างบุ๊กมาร์ก" collapseAll = "ยุบบุ๊กมาร์กทั้งหมด" +empty = "No bookmarks in this document" expandAll = "ขยายบุ๊กมาร์กทั้งหมด" searchPlaceholder = "ค้นหาบุ๊กมาร์ก" +title = "Bookmarks" [viewer.comments] addComment = "เพิ่มความคิดเห็น" @@ -11264,10 +11297,12 @@ locateAnnotation = "ค้นหาตำแหน่งในเอกสาร moreActions = "การดำเนินการเพิ่มเติม" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "หน้า {{page}}" placingHint = "คลิกที่หน้ากระดาษเพื่อวาง… (ยกเลิก)" removeCommentOnly = "ลบเฉพาะความคิดเห็น" saveReply = "บันทึกการตอบกลับ" +searchPlaceholder = "Search comments" title = "ความคิดเห็น" typeComment = "ความคิดเห็น" typeInsertText = "แทรกข้อความ" @@ -11290,6 +11325,7 @@ unsavedDesc = "คุณมีการเปลี่ยนแปลงที closeSidebar = "ปิดแถบด้านข้างเลเยอร์" hideAll = "ซ่อนเลเยอร์ทั้งหมด" showAll = "แสดงเลเยอร์ทั้งหมด" +title = "Layers" [viewer.link] delete = "ลบลิงก์" @@ -11646,6 +11682,7 @@ exitRedaction = "ออกจากโหมดปกปิดข้อมูล exportAll = "ส่งออก PDF" exportSelected = "ส่งออกหน้าที่เลือก" formFill = "กรอกแบบฟอร์ม" +hideToolbar = "Hide toolbar" multiTool = "เครื่องมือหลายฟังก์ชัน" panMode = "โหมดเลื่อน" print = "พิมพ์ PDF" @@ -11666,6 +11703,7 @@ selectAll = "เลือกทั้งหมด" selectByNumber = "เลือกตามหมายเลขหน้า" selectLanguage = "เลือกภาษา" share = "แชร์" +showToolbar = "Show toolbar" toggleAnnotations = "สลับการแสดงคำอธิบายประกอบ" toggleAttachments = "สลับไฟล์แนบ" toggleBookmarks = "สลับที่คั่นหน้า" diff --git a/frontend/editor/public/locales/tr-TR/translation.toml b/frontend/editor/public/locales/tr-TR/translation.toml index f387e2d9b5..a1a1acd4f8 100644 --- a/frontend/editor/public/locales/tr-TR/translation.toml +++ b/frontend/editor/public/locales/tr-TR/translation.toml @@ -1,7 +1,6 @@ alphabet = "Alfabe" apply = "Uygula" applyAndContinue = "Kaydet ve Çık" -areYouSure = "Ayrılmak istediğinizden emin misiniz?" back = "Geri" black = "Siyah" blue = "Mavi" @@ -106,6 +105,7 @@ undoSuccess = "İşlem başarıyla geri alındı" unknown = "Bilinmeyen" unpin = "Dosya Sabitlemesini Kaldır (araç çalıştıktan sonra değiştir)" unsavedChanges = "PDF'inizde kaydedilmemiş değişiklikler var." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Kaydedilmemiş Değişiklikler" unsupported = "Desteklenmiyor" username = "Kullanıcı Adı" @@ -1879,6 +1879,9 @@ width = "Genişlik" [app] description = "Ücretsiz Adobe Acrobat alternatifi (10M+ indirme)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "PDF/A-3b'ye dönüştür" convertToPdfA3bDescription = "Gömülü eklerle arşivlik bir PDF oluşturur" @@ -2933,12 +2936,16 @@ text = "Bu izinlerin değiştirilemez olmasını sağlamak için, sahip parolas [chat.actions] copy = "Mesajı kopyala" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling ajan seçenekleri" +assistant = "AI Assistant" clearChat = "Sohbeti temizle" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Bu işlem bulut kredilerinizi kullanacak" tooltip = "Ekrandan renk seç" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Renk seçin" [common] +actions = "Actions" back = "Geri" cancel = "İptal" close = "Kapat" +codeSample = "Code sample" collapse = "Daralt" confirm = "Onayla" continue = "Devam et" copied = "Kopyalandı!" copy = "Kopyala" done = "Bitti" +enterValue = "Enter value" error = "Hata" expand = "Genişlet" loading = "Yükleniyor..." next = "Sonraki" +open = "Open" preview = "Önizleme" previous = "Önceki" refresh = "Yenile" remaining = "Kalan" retry = "Yeniden dene" save = "Kaydet" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Seçileni temizle" @@ -3185,6 +3199,7 @@ title = "Sıkıştırma Yöntemi" [compress.settings] desiredSize = "İstenen Dosya Boyutu" desiredSizePlaceholder = "Boyutu girin" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Sıkıştırma, dosya boyutunu azaltmanın kolay bir yoludur. Hedef boyut girmek ve kaliteyi bizim ayarlamamız için Dosya Boyutu'nu seçin. Sıkıştırma gücünü elle ayarlamak için Kalite'yi seçin." @@ -3387,6 +3402,24 @@ pdfOptions = "PDF Seçenekleri" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Önce bir kaynak biçimi seçin" settings = "Ayarlar" single = "Tek" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Ara..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Düzenle" @@ -3824,11 +3861,13 @@ shareSelected = "Seçileni Paylaş" sharing = "Paylaşım" showAll = "Tümünü Göster" showHistory = "Geçmişi Göster" +sortBy = "Sort files" sortByDate = "Tarihe Göre Sırala" sortByName = "Ada Göre Sırala" sortBySize = "Boyuta Göre Sırala" storage = "Depolama" storageState = "Depolama" +storageUsed = "Storage used" synced = "Eşitlendi" title = "PDF Dosyaları Yükle" toolChain = "Uygulanan Araçlar" @@ -3874,8 +3913,12 @@ addFiles = "Dosya ekle" addingFiles = "Dosyalar ekleniyor…" collapse = "Kenar çubuğunu daralt" customizeGroups = "Grupları özelleştir" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Başlamak için dosya açın" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Kenar çubuğunu genişlet" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive yapılandırılmamış" @@ -3888,15 +3931,17 @@ openFromComputer = "Bilgisayardan aç" openSettings = "Ayarları aç" other = "Diğer" recent = "Son" -search = "Ara" -searchPlaceholder = "Dosyalarda ara..." viewAll = "{{count}} dosyanın tümünü görüntüle" [fileSidebar.fileItem] closeViewer = "Görüntüleyiciyi kapat" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Görüntüleyicide aç" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Varsayılanlara sıfırla" subtitle = "Dosyalarınızı üst kategoriler halinde gruplandırın. Bir kategoriye mevcut veya yeni etiketler ekleyin, yeniden adlandırın ya da kendi kategorinizi oluşturun. Kategorilerinizin hiçbirinde olmayan dosyalar “Diğer” altında görünür." title = "Kenar çubuğu kategorileri" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Çalışma alanına ekle" addToWorkspaceCount = "{{count}} öğeyi çalışma alanına ekle" @@ -3918,7 +3971,6 @@ backToMyFiles = "Dosyalarıma geri dön" breadcrumbs = "Klasör yolu" cancel = "İptal" classification = "Sınıflandırma" -clearSearch = "Aramayı temizle" clearSelection = "Seçimi temizle" closeDetails = "Ayrıntıları kapat" create = "Oluştur" @@ -3950,6 +4002,7 @@ downloadAll = "Tümünü indir" downloadVersion = "Bu sürümü indir" dropOverlay = "Yüklemek için dosyaları bırakın" dropOverlaySub = "Dosyalar Yerel'de başlar. Onları bir klasöre düzenlemek için 'Taşı' veya 'Buluta kaydet'i kullanın." +duplicate = "Duplicate" file = "Dosya" fileInfo = "File info" fileMenu = "Dosya eylemleri" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Klasör ağacını yeniden boyutlandır (yön tuşları, bü save = "Kaydet" saveToServer = "Sunucuya kaydet" saveToServerDisabledHint = "Bu sunucuda sunucuya kaydetme etkin değil. Yöneticinizden etkinleştirmesini isteyin." -search = "Ara" -searchPlaceholder = "Bu klasörde ve alt klasörlerde ara" selectAll = "Tümünü seç" selectAllHint = "Tümünü seçmek için tıklayın. İpucu: Tek tek dosya eklemek için Ctrl (veya Cmd) basılı tutun, aralık seçmek için Shift." selectedCount = "{{count}} seçildi" @@ -4044,6 +4095,10 @@ title = "Henüz bulut dosyası yok" hint = "Yüklemeden kaydedilen dosyalar burada kalır. Eklemek için bir dosya bırakın." title = "Yalnızca yerel dosya yok" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Açtığınız veya düzenlediğiniz dosyalar burada görünecek." title = "Henüz değiştirilen yok" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Klasör silinemedi." deleteFolderFailedDetail = "Klasör silinemedi: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Klasör görünümü güncellenemedi." folderAppearanceFailedDetail = "Klasör görünümü güncellenemedi: {{message}}" moveFilesFailed = "Dosyalar taşınamadı." @@ -4113,6 +4170,11 @@ localHint = "Sadece bu tarayıcıda depolanır" shared = "Paylaşılan" sharedHint = "Bağlantıyla sizinle paylaşılmış" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "En eskiden" modifiedDesc = "En yeniden" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Depolamada dosya yok. Önce bazı dosyaları açın." open = "Aç" openFile = "Dosya Aç" openFiles = "Dosyalar Aç" +selectFile = "Select {{name}}" selectFromStorage = "Depolamadan Seç" upload = "Yükle" uploadFile = "Dosya Yükle" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Tümü kaydedildi" +analyzingFields = "Analysing form fields..." extractCsvError = "CSV çıkarılamadı" extractXlsxError = "XLSX çıkarılamadı" +filled = "filled" flattenAfterFilling = "Doldurduktan sonra düzleştir" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "zor" requiredFieldsError = "Lütfen tüm zorunlu alanları doldurun" rescanFields = "Alanları yeniden tara" @@ -4795,9 +4863,6 @@ title = "Resimden PDF'e" [imageToPdf] tags = "dönüşüm,img,jpg,fotoğraf,resim" -[infoBanner] -dismiss = "Kapat" - [invite] acceptError = "Hesap oluşturulamadı" accountFor = "Şunun için hesap oluşturuluyor" @@ -4979,6 +5044,34 @@ uploadSuccess = "Yükleme Başarılı!" uploadSuccessMessage = "Görselleriniz aktarıldı." validating = "Oturum doğrulanıyor..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Fotoğrafları yüklemek için tarayın. Görseller otomatik olarak PDF'ye dönüştürülür." descriptionNoConvert = "Mobil cihazınızdan fotoğrafları yüklemek için tarayın." @@ -4997,8 +5090,13 @@ title = "Mobil Cihazdan Yükle" tags = "Çoklu Araç,Çoklu işlem,Arayüz,tıklama sürükleme,ön uç,istemci tarafı,etkileşimli,taşınabilir,taşı" title = "PDF Çoklu Araç" -[navbar] -search = "Ara" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Kimlik doğrulama başarılı olmadı. Bu pencereyi kapatıp tekrar deneyebilirsiniz." @@ -5127,6 +5225,7 @@ activeFiles = "Aktif Dosyalar görünümü, araca yüklediğini allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Kapat" cropSettings = "Artık kırpmak istediğimiz dosyayı seçtiğimize göre, PDF'nin kırpılacağı alanı seçmek için Kırp aracını yapılandırabiliriz." +dialogLabel = "Onboarding" fileCheckbox = "Dosyalardan birine tıklamak, onu işleme için seçer. Toplu işlemler için birden fazla dosya seçebilirsiniz." fileReplacement = "Değiştirilen dosya, Çalışma Alanı'nda orijinal dosyanın yerini otomatik olarak alır; böylece onu kolayca başka araçlardan da geçirebilirsiniz." filesButton = "Hızlı Erişim çubuğundaki Dosyalar düğmesi, araçları kullanmak üzere PDF yüklemenize olanak tanır." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF'den PDF/A'ya" tags = "arşiv,uzun vadeli,standart,dönüşüm,saklama,koruma" title = "PDF'den PDF/A'ya" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "yazdırma,standart,dönüştürme,üretim,baskı öncesi,arşiv" title = "PDF'den PDF/X'e" @@ -6045,6 +6151,7 @@ small = "500 Kredi" xsmall = "100 Kredi" [plan.availablePlans] +currency = "Billing currency" subtitle = "İhtiyaçlarınıza uygun planı seçin" title = "Mevcut Planlar" @@ -6624,6 +6731,7 @@ reachedTitle = "Aylık harcama limitine ulaşıldı" title = "Stripe portalı açılamadı" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "{{allowance}} ücretsiz PDF hakkından kullanıldı" capSuffix_other = "{{allowance}} ücretsiz PDF hakkından kullanıldı" eyebrow = "Processor denemesi" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Bir ilke çalıştığında bir belgeyi veya bildirimi email ile gönderin." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "İşlenen belgeleri Nextcloud'a dosyalayın." @@ -7077,11 +7211,6 @@ title = "Resmi SDK'lar" beta = "Beta" deprecated = "Kullanımdan kaldırıldı" -[portal.docs.search] -empty = "Eşleşen doküman yok" -placeholder = "Dokümanlarda ara" -results = "{{count}} sonuç" - [portal.docs.skills] eyebrow = "BECERİLER" lead = "Ajanınızın tek bir araç olarak çağırdığı paketlenmiş, adlandırılmış yetenekler. Her beceri, eval'ları ekli deterministik bir op zinciridir." @@ -7180,11 +7309,11 @@ editorAction = "Düzenleyici" empty = "Bu filtreyle eşleşen belge yok." rowActions = "Satır eylemleri" sensitiveLabel = "Hassas" -sensitiveTitle = "Hassas — erişim gerekli" [portal.documents.table.columns] action = "Pipeline / Eylem" document = "Belge" +labels = "Labels" product = "Ürün" status = "Durum" time = "Zaman" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Son görülme" region = "Bölge" status = "Durum" +target = "Target" version = "Sürüm" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Bu görünüm beklenmeyen bir hatayla karşılaştı. Tekrar dene retry = "Tekrar dene" title = "Bu sayfada bir şeyler ters gitti" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Geri" body = "Masaüstü uygulamasını indirin veya sunucuyu kendi ortamınızda barındırın." @@ -7380,11 +7560,6 @@ cancel = "İptal" confirm = "Anahtarı iptal et" title = "API anahtarını iptal et" -[portal.infrastructure.attestationLabel] -attested = "Doğrulanmış" -inScope = "Kapsamda" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Denetim etkinliklerini kategoriye göre filtrele" heading = "Denetim günlükleri" @@ -7456,11 +7631,6 @@ info = "Bilgi" success = "Başarılı" warning = "Uyarı" -[portal.infrastructure.certLabel] -certified = "Sertifikalı" -inProgress = "Devam ediyor" -notStarted = "Başlatılmadı" - [portal.infrastructure.createKey] cancel = "İptal" createKey = "Anahtar oluştur" @@ -7474,238 +7644,10 @@ subtitleCreated = "Bu sırrı şimdi kopyalayın — tekrar gösterilmeyecek." title = "API anahtarı oluştur" titleCreated = "Anahtar oluşturuldu" -[portal.infrastructure.deployLabel] -live = "Canlı" -queued = "Kuyrukta" -rolledBack = "Geri alındı" -rolling = "Yayınlanıyor" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/dk" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Dağıtan" -environment = "Ortam" -product = "Ürün" -status = "Durum" -version = "Sürüm" -when = "Ne zaman" - -[portal.infrastructure.deployments.recent] -heading = "Son dağıtımlar" -subheading = "Ürünler ve ortamlar genelindeki en son kullanıma almalar." - -[portal.infrastructure.deployments.regionColumns] -instances = "Örnekler" -latency = "Gecikme" -load = "Yük" -p99 = "P99" -region = "Bölge" -status = "Durum" -throughput = "Aktarım hızı" -uptime = "Çalışma süresi" -version = "Sürüm" - -[portal.infrastructure.deployments.regions] -heading = "Bölgeler" -subheading = "Dağıtılmış her Stirling bölgesi için canlı sağlık — gecikme, yük ve rollout sürümü." - -[portal.infrastructure.deployments.regions.empty] -description = "Çalışma alanınız hazırlandıktan sonra dağıtılmış bölgeler burada görünür." -title = "Dağıtılmış bölge yok" - [portal.infrastructure.keyLabel] active = "Aktif" revoked = "İptal edildi" -[portal.infrastructure.modelLabel] -active = "Aktif" -degraded = "Bozulmuş" -disabled = "Devre dışı" - -[portal.infrastructure.models] -heading = "Modeller" -msValue = "{{value}} ms" -subheading = "Çalışma alanınız genelinde belge işlemeyi destekleyen model kataloğu ve yönlendirme." - -[portal.infrastructure.models.byom] -description = "On-prem veya self-hosted bir modeli kaydedin ve veri yerleşimiyle sınırlı işleme için bir bölgeye sabitleyin." -title = "Kendi modelinizi getirin" - -[portal.infrastructure.models.catalogue] -heading = "Katalog" -sub = "Çalışma alanınız için kullanılabilir yönetilen modeller, canlı gecikme ve maliyetle birlikte." -subEnterprise = "Yönetilen, kendi getirdiğiniz ve on-prem modeller — bölge bazında sabitleme kullanılabilir." - -[portal.infrastructure.models.catalogue.empty] -description = "Çalışma alanınızın kataloğundaki modeller burada görünür." -title = "Kullanılabilir model yok" - -[portal.infrastructure.models.columns] -cost = "Maliyet" -latency = "Gecikme" -load = "Yük" -model = "Model" -status = "Durum" -type = "Tür" -version = "Sürüm" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/çağrı" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Aktif modeller" -avgLatency = "Ort. gecikme" -included = "Dahil" -monthlySpend = "Aylık model harcaması" - -[portal.infrastructure.models.routing] -empty = "Yapılandırılmış yönlendirme kuralı yok." -heading = "Yönlendirme kuralları" -sub = "Her işlemi hangi modelin yönettiği. Daha dar bir kural eşleşmediğinde varsayılan uygulanır." -subLocked = "İşlemleri belirli modellere yönlendirin — ücretli planlarda kullanılabilir." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Her işlemi ve belge türünü hangi modelin yöneteceğini kontrol etmek için Pro'ya yükseltin." -title = "Model yönlendirme ücretli bir özelliktir" - -[portal.infrastructure.models.routingColumns] -default = "Varsayılan" -docType = "Belge türü" -modelForAria = "{{operation}} için model" -operation = "İşlem" -routedTo = "Yönlendirildiği yer" - -[portal.infrastructure.modelTypeLabel] -classification = "Sınıflandırma" -extraction = "Çıkarma" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Bozulmuş" -down = "Kapalı" -healthy = "Sağlıklı" - -[portal.infrastructure.security.access.byok] -description = "Kendi KMS'nizden bir anahtar sağlayın. Stirling bununla şifreler ancak yine de okuyabilir." -label = "Kendi anahtarınızı getirin (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Anahtarlar KMS'nizden asla ayrılmaz. Stirling yalnızca şifreli metni tutar." -label = "Kendi anahtarınızı tutun (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling şifreleme anahtarlarını yönetir. En basiti — sizin tarafınızda sıfır anahtar operasyonu." -label = "Stirling tarafından tutulan anahtarlar" - -[portal.infrastructure.security.accessPolicy] -heading = "Belge erişim ilkesi" -subheading = "İşlenen belgelerin beklemedeyken şifresini kimlerin çözebileceğini kontrol eder." - -[portal.infrastructure.security.attestations] -heading = "Uyumluluk beyanları" -noReport = "Rapor mevcut değil" -subheading = "Beyan edilmiş kontrollerde raporların bulunduğu, çerçeve bazında denetim duruşu." -viewReport = "Raporu görüntüle →" - -[portal.infrastructure.security.compliance] -heading = "Uyumluluk" -subheading = "Stirling platformunu kapsayan beyanlar ve sertifikasyonlar." - -[portal.infrastructure.security.empty] -description = "Çalışma alanınızın güvenlik yapılandırması burada görünür." -title = "Güvenlik duruşu kullanılamıyor" - -[portal.infrastructure.security.hyokBanner] -description = "HYOK ile şifreleme anahtarları KMS'nizden asla ayrılmaz. Stirling yalnızca istediğiniz zaman iptal edebileceğiniz şifreli metni saklar ve işler." -title = "Stirling belgelerinizin şifresini çözemez" - -[portal.infrastructure.security.ipAllowlist] -empty = "Yapılandırılmış IP aralığı yok — tüm IP'lere izin verilir." -heading = "IP izin listesi" -sub = "API erişimi bu CIDR aralıklarıyla sınırlandırılmıştır." -subLocked = "API erişimini bilinen IP aralıklarıyla sınırlandırın — ücretli planlarda kullanılabilir." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "API erişimini belirli ağlarla sınırlandırmak için Pro'ya yükseltin." -title = "IP izin listeleme ücretli bir özelliktir" - -[portal.infrastructure.security.ipColumns] -added = "Eklendi" -addedBy = "Ekleyen" -cidr = "CIDR" -label = "Etiket" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algoritma" -heading = "Şifreleme anahtarı yönetimi" -keyId = "Anahtar tanımlayıcısı" -lastRotated = "Son döndürme" -rotateKey = "Anahtarı döndür" -rotationPolicy = "Döndürme ilkesi" -subheading = "Beklemedeki belgeleri şifreleyen anahtarların saklama sorumluluğu — kim şifre çözebilir ve anahtarlar nasıl döndürülür." - -[portal.infrastructure.security.managedBanner] -description = "Kendi anahtarını getir (BYOK) ve kendi anahtarını tut (HYOK) saklama sorumluluğu Enterprise'da kullanılabilir. Kendi KMS'nizden anahtar sağlamak için yükseltin." -title = "Planınızda anahtarlar Stirling tarafından yönetilir" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Asya Pasifik" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR veri sınırı" -label = "Avrupa Birliği" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Amerika Birleşik Devletleri" - -[portal.infrastructure.security.residencyHeader] -heading = "Veri yerleşimi" -subheading = "Belgelerin depolandığı ve işlendiği yer." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} kullanıldı" - -[portal.infrastructure.storage.empty] -description = "Bağlı depolama ve kullanım burada görünür." -title = "Yapılandırılmış depolama yok" - -[portal.infrastructure.storage.lifecycle] -active = "Aktif" -activeRange = "0–{{value}}g" -archived = "Arşivlendi" -coldStorage = "soğuk depolama" -deleted = "Silindi" -never = "asla" -purged = "kalıcı olarak silindi" - -[portal.infrastructure.storage.providers] -connect = "Bağlan" -connected = "Bağlandı" -heading = "Bağlı sağlayıcılar" -subheading = "İşlenen çıktılarının yazıldığı yer." - -[portal.infrastructure.storage.retention] -heading = "Saklama" -subheading = "Yaşam döngüsü silinmesinden önce çıktıların ne kadar süre tutulacağı." -windowLabel = "Varsayılan saklama penceresi" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} gün" -days_other = "{{count}} gün" -never = "Asla silme" - -[portal.infrastructure.storage.totalUsage] -heading = "Toplam kullanım" -progressLabel = "Kullanılan depolama" -subheading = "Tüm bağlı sağlayıcılar genelinde tüketilen depolama." - [portal.infrastructure.tabs] apiKeys = "API Anahtarları" audit = "Denetim Günlükleri" @@ -7715,11 +7657,9 @@ security = "Güvenlik" storage = "Depolama" [portal.integrations] -addAnother = "Başka ekle" availableHeading = "Kullanılabilir" comingSoonHeading = "Yakında" connect = "Bağlan" -connectedHeading = "Bağlı" connectionCount_one = "{{count}} bağlantı" connectionCount_other = "{{count}} bağlantı" customApi = "Özel API" @@ -7737,6 +7677,10 @@ security = "Güvenlik" signing = "İmzalama" storage = "Depolama" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Bağlı" @@ -7767,7 +7711,6 @@ integrations = "Entegrasyonlar" pipelines = "Pipelines" policies = "İlkeler" procurement = "Tedarik" -settings = "Ayarlar" sources = "Kaynaklar" usage = "Kullanım ve Faturalandırma" users = "Kullanıcılar" @@ -7784,7 +7727,7 @@ title = "Pipelines" newPipeline = "Yeni pipeline" [portal.pipelines.builder] -addStep = "Araç ekle" +activate = "Activate" back = "Pipeline'lara dön" cannotFollow = "{{produced}} alınamaz" chooseAccount = "Bir hesap seçin" @@ -7792,28 +7735,38 @@ chooseDestination = "Bir hedef seçin" chooseOperation = "Bu adımın ne yapacağını seçin" chooseSource = "Bir kaynak seçin" discard = "Değişiklikleri at" -enabled = "Etkin" inputs = "Girdi" inputSource = "Girdi kaynağı" inputTrigger = "Tetikleyici" keepEditing = "Düzenlemeye devam et" +moreActions = "More actions" needsConfiguring = "Kurulum gerekli" -needsUpload = "Yüklenmiş bir dosya gerekiyor" -noSources = "Henüz bağlı kaynak yok. Girdi olarak kullanmak için bir tane oluşturun." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Aramanızla eşleşen araç yok." -pipelineSettings = "Pipeline ayarları" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Araç ara" -selectToolBody = "Pipeline'ınızı oluşturmak için bir araç ekleyin." -selectToolTitle = "Henüz araç yok" sendToSystem = "Başka bir sisteme gönder" stepsIncompatible = "Bu adımlar, önceki adımlarının ürettiği üzerinde çalışamaz: {{tools}}." stepsNeedSetup = "Kaydetmeden önce bu adımların hâlâ kurulması gerekiyor: {{tools}}." -toolSettings = "Araç ayarları" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Tanınmayan işlem, olduğu gibi tutuldu." unsavedBody = "Kaydedilmemiş değişiklikleriniz var. Ayrılmadan önce kaydedin mi, yoksa atılsın mı?" unsavedTitle = "Kaydedilmemiş değişiklikler" -uploadUnsupported = "Yüklenen dosyalar henüz pipeline'larda desteklenmiyor, bu nedenle şu adımlar kaydedilemez: {{tools}}." usesDefaults = "Varsayılan ayarlarla çalışır" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Önceki adımdaki her dosyayı birleştirir" @@ -7825,31 +7778,36 @@ undeclared-operation = "Bu adımın ne kabul ettiği denetlenemiyor" [portal.pipelines.composer] addTool = "Araç ekle" -cancel = "İptal" -chainEmpty = "Pipeline'ınızı oluşturmaya başlamak için bir araç ekleyin." create = "Pipeline oluştur" +createPaused = "Create paused" editingUnsupported = "Bu araç parametrelerinin düzenleme için görüntülenmesi henüz desteklenmiyor." -moveDown = "Aşağı taşı" -moveUp = "Yukarı taşı" +editSource = "Edit source" name = "Ad" namePlaceholder = "örn. Redaction taraması" noToolSettings = "Bu aracın yapılandırılabilir ayarı yok." -operations_one = "İşlem ({{count}})" -operations_other = "İşlemler ({{count}})" output = "Çıktı" -removeStep = "İşlemi kaldır" save = "Değişiklikleri kaydet" scheduleEvery = "Şu aralıkla çalıştır" -sources = "Kaynaklar" -sourcesLoading = "Kaynaklar yükleniyor..." trigger = "Tetikleyici" triggerManual = "Yalnızca manuel" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "gün" hours = "saat" minutes = "dakika" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "\"{{name}}\" silinsin mi? Bu geri alınamaz." cancel = "İptal" @@ -7867,6 +7825,37 @@ connectSource = "Bir kaynak bağla" description = "İlk pipeline'ınızı oluşturun: çalışacağı kaynakları seçin, işlemleri zincirleyin ve çıktının nereye gideceğini belirleyin." title = "Henüz pipeline yok" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Aktif" paused = "Duraklatıldı" @@ -7879,7 +7868,6 @@ completed_one = "Çalıştırma tamamlandı." completed_other = "{{count}} çalıştırmanın tümü tamamlandı." empty = "Çalıştırılacak bir şey yok: kaynaklarda işlenecek belge yoktu." failed = "Çalıştırma başarısız: {{error}}" -historyCleared = "Geçmiş temizlendi. Sonraki çalıştırma, şu anda kaynaklarda bulunan her şeyi yeniden işler." inFlight = "Çalıştırılacak yeni bir şey yok: belgeler önceki bir çalıştırmadan hâlâ işleniyor." parked_one = "Çalıştırılacak bir şey yok: {{count}} belge daha önce başarısız oldu ve park edildi. Nedeni düzeltin, ardından yeniden denemek için geçmişi temizleyin." parked_other = "Çalıştırılacak bir şey yok: {{count}} belge daha önce başarısız oldu ve park edildi. Nedeni düzeltin, ardından yeniden denemek için geçmişi temizleyin." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Kaynaklar" status = "Durum" steps = "Adımlar" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Klasör izleme" @@ -8209,6 +8198,24 @@ label = "Bir Jira issue'suna ekle" description = "İşlenen belgeyi ek olarak gönderir." label = "Belgeyi e-posta ile gönder (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "İşlenen belgeyi bir klasöre yazar." label = "Nextcloud'a yükle" @@ -8683,16 +8690,6 @@ maxed = "Sınıra ulaşıldı" subtitle = "Ücretsiz denemeniz {{date}} tarihine kadar sürer. Kart gerekmez." title = "Enterprise denemesi" -[portal.search] -ariaLabel = "Ara" -placeholder = "Stirling'de ara — endpoints, pipelines, docs…" - -[portal.search.empty] -noActionsDescription = "Hızlı eylemler kullanılabilir olduğunda burada görünecek." -noActionsTitle = "Hızlı eylem yok" -noMatches = "\"{{query}}\" için eşleşme yok" -noMatchesDescription = "Farklı bir anahtar kelime deneyin veya kataloğa göz atın." - [portal.settings.groups] admin = "Yönetici" @@ -8743,10 +8740,6 @@ cancel = "İptal" confirm = "Sil" title = "Kaynak silinsin mi?" -[portal.sources.empty] -description = "İlkelerinizin belgeleri alabileceği bir yer olması için bir klasör (ve yakında bulut depolama) bağlayın." -title = "Henüz bağlı kaynak yok" - [portal.sources.kpi] inUse = "Kullanımda" total = "Bağlantılar" @@ -8785,6 +8778,7 @@ unused = "Kullanılmıyor" documents = "Belgeler" source = "Kaynak" status = "Durum" +type = "Type" usedBy = "İlkeler" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Politikalar ve Gizlilik" [settings.preferences] title = "Tercihler" -[settings.search] -placeholder = "Ayar sayfalarında ara..." - [settings.security] description = "Hesabınızı güvende tutmak için şifrenizi güncelleyin." title = "Güvenlik" @@ -10128,6 +10119,7 @@ last = "Son sayfa" maintainRatio = "Oranı korumayı değiştir" next = "Sonraki sayfa" noSavedSigs = "Kayıtlı imza bulunamadı" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Kişisel İmzalar" previous = "Önceki sayfa" redo = "Yinele" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Arka plan kaldırılamadı" hint = "İmzanızın PNG veya JPG görselini yükleyin" label = "İmza görseli yükle" placeholder = "Görsel dosyası seç" +previewAlt = "Current image signature" processing = "Görsel işleniyor..." removeBackground = "Beyaz arka planı kaldır (şeffaf yap)" @@ -10179,6 +10172,17 @@ saved = "Yukarıdan kaydedilmiş bir imza seçin, ardından PDF üzerinde herhan text = "Adınızı yukarıya girdikten sonra, imzanızı PDF üzerinde herhangi bir yere tıklayarak yerleştirin." title = "İmza nasıl eklenir" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "İmzayı Taşı" pause = "Yerleştirmeyi duraklat" @@ -10625,6 +10629,23 @@ title = "Sunucuya Yükle" updateButton = "Sunucuda Güncelle" uploadButton = "Sunucuya Yükle" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Geri dön" pdfTools = "PDF Araçları" placeholder = "Başlamak için bir araç seçin" premiumFeature = "Premium özellik:" -searchTools = "Araçları ara" toolsHeader = "Araçlar" viewAllTools = "Tüm araçları görüntüle" @@ -10822,6 +10842,9 @@ removal = "Kaldırma" signing = "İmzalama" verification = "Doğrulama" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Araç bulunamadı" noTools = "Kullanılabilir araç yok" @@ -10979,6 +11002,13 @@ approver = "İlkeyi onaylar" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "{{email}} adresine gönderilen davetiye iptal edilsin mi? Mevcut bağlantıyla katılamayacaklar." cancelInviteTitle = "Davetiyeyi iptal et" @@ -10998,10 +11028,8 @@ title = "Henüz üye yok" addToTeam = "Takıma ekle" guestCount = "{{count}} misafir" guests = "Misafirler" -guestsDesc = "Dış iş ortakları, paylaştıklarınızla sınırlıdır. Yalnızca Editor." ledBy = "{{owner}} tarafından yönetiliyor" org = "Kuruluş" -orgDesc = "Kuruluş genelinde yetkiye ve ilke onayına sahip sahipler" owners = "{{count}} sahip" team = "{{name}} takımı" teamMeta = "{{count}} kişi" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Davet eden: {{who}}" cancel = "İptal" -count = "{{count}} beklemede" -desc = "Henüz katılmamış davet edilen kişiler. Kabul edene kadar bir koltuk tutarlar." expiresInDays_one = "{{count}} gün içinde sona erer" expiresInDays_other = "{{count}} gün içinde sona erer" expiresToday = "Bugün sona erer" title = "Bekleyen davetiyeler" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Arka uca ulaşılırken bir sorun oluştu veya erişiminiz yok. Tekrar deneyin." title = "Üyeler yüklenemedi" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Eklerde ara" title = "Ekler" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Yer imi başlığı" bookmarkTitleRequired = "Yer imi başlığı gereklidir" closeSidebar = "Yer imleri kenar çubuğunu kapat" collapseAll = "Tüm yer imlerini daralt" +empty = "No bookmarks in this document" expandAll = "Tüm yer imlerini genişlet" searchPlaceholder = "Yer imlerini ara" +title = "Bookmarks" [viewer.comments] addComment = "Yorum ekle" @@ -11264,10 +11297,12 @@ locateAnnotation = "Belgede konumlandır" moreActions = "Diğer işlemler" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Sayfa {{page}}" placingHint = "Yerleştirmek için bir sayfaya tıklayın… (iptal)" removeCommentOnly = "Yalnızca yorumu kaldır" saveReply = "Yanıtı kaydet" +searchPlaceholder = "Search comments" title = "Yorumlar" typeComment = "Yorum" typeInsertText = "Metin Ekle" @@ -11290,6 +11325,7 @@ unsavedDesc = "Kaydedilmemiş değişiklikleriniz var" closeSidebar = "Katmanlar kenar çubuğunu kapat" hideAll = "Tüm katmanları gizle" showAll = "Tüm katmanları göster" +title = "Layers" [viewer.link] delete = "Bağlantıyı sil" @@ -11646,6 +11682,7 @@ exitRedaction = "Karartma modundan çık" exportAll = "PDF'yi dışa aktar" exportSelected = "Seçili sayfaları dışa aktar" formFill = "Form doldur" +hideToolbar = "Hide toolbar" multiTool = "Çoklu araç" panMode = "Kaydırma modu" print = "PDF'yi yazdır" @@ -11666,6 +11703,7 @@ selectAll = "Tümünü seç" selectByNumber = "Sayfa numaralarına göre seç" selectLanguage = "Dil seç" share = "Paylaş" +showToolbar = "Show toolbar" toggleAnnotations = "Ek açıklamaların görünürlüğünü değiştir" toggleAttachments = "Ekleri göster/gizle" toggleBookmarks = "Yer imlerini göster/gizle" diff --git a/frontend/editor/public/locales/uk-UA/translation.toml b/frontend/editor/public/locales/uk-UA/translation.toml index bb22c61fe6..aa777bf20a 100644 --- a/frontend/editor/public/locales/uk-UA/translation.toml +++ b/frontend/editor/public/locales/uk-UA/translation.toml @@ -1,7 +1,6 @@ alphabet = "Алфавіт" apply = "Застосувати" applyAndContinue = "Зберегти й вийти" -areYouSure = "Ви впевнені, що хочете вийти?" back = "Назад" black = "Чорний" blue = "Синій" @@ -106,6 +105,7 @@ undoSuccess = "Операцію успішно скасовано" unknown = "Невідомо" unpin = "Відкріпити файл (замінити після виконання інструмента)" unsavedChanges = "У вас є незбережені зміни у вашому PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Незбережені зміни" unsupported = "Не підтримується" username = "Ім'я користувача" @@ -1879,6 +1879,9 @@ width = "Ширина" [app] description = "Безкоштовна альтернатива Adobe Acrobat (10M+ завантажень)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Перетворити в PDF/A-3b" convertToPdfA3bDescription = "Створює архівний PDF із вбудованими вкладеннями" @@ -2933,12 +2936,16 @@ text = "Щоб зробити ці дозволи незмінними, скор [chat.actions] copy = "Копіювати повідомлення" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Параметри агента Stirling" +assistant = "AI Assistant" clearChat = "Очистити чат" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Ця операція використає ваші хмарні кр tooltip = "Вибрати колір з екрана" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Виберіть колір" [common] +actions = "Actions" back = "Назад" cancel = "Скасувати" close = "Закрити" +codeSample = "Code sample" collapse = "Згорнути" confirm = "Підтвердити" continue = "Продовжити" copied = "Скопійовано!" copy = "Копіювати" done = "Готово" +enterValue = "Enter value" error = "Помилка" expand = "Розгорнути" loading = "Завантаження..." next = "Далі" +open = "Open" preview = "Попередній перегляд" previous = "Назад" refresh = "Оновити" remaining = "Залишилося" retry = "Повторити" save = "Зберегти" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Очистити вибране" @@ -3185,6 +3199,7 @@ title = "Метод стиснення" [compress.settings] desiredSize = "Бажаний розмір файлу" desiredSizePlaceholder = "Введіть розмір" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Стиснення — простий спосіб зменшити розмір файлу. Виберіть «Розмір файлу», щоб вказати цільовий розмір і ми підлаштуємо якість за вас. Або виберіть «Якість», щоб вручну задати силу стиснення." @@ -3387,6 +3402,24 @@ pdfOptions = "Параметри PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Спочатку виберіть вихідний формат" settings = "Налаштування" single = "Один" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Пошук..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Редагувати" @@ -3824,11 +3861,13 @@ shareSelected = "Поділитися вибраними" sharing = "Спільний доступ" showAll = "Показати все" showHistory = "Показати історію" +sortBy = "Sort files" sortByDate = "Сортувати за датою" sortByName = "Сортувати за назвою" sortBySize = "Сортувати за розміром" storage = "Сховище" storageState = "Сховище" +storageUsed = "Storage used" synced = "Синхронізовано" title = "Завантажити файли PDF" toolChain = "Застосовані інструменти" @@ -3874,8 +3913,12 @@ addFiles = "Додати файли" addingFiles = "Додавання файлів…" collapse = "Згорнути бічну панель" customizeGroups = "Налаштувати групи" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Відкрийте файли, щоб почати" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Розгорнути бічну панель" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive не налаштовано" @@ -3888,15 +3931,17 @@ openFromComputer = "Відкрити з комп’ютера" openSettings = "Відкрити налаштування" other = "Інше" recent = "Нещодавні" -search = "Пошук" -searchPlaceholder = "Пошук файлів..." viewAll = "Переглянути всі файли ({{count}})" [fileSidebar.fileItem] closeViewer = "Закрити переглядач" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Відкрити в переглядачі" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Скинути до стандартних налаштувань" subtitle = "Групуйте файли в батьківські категорії. Додавайте наявні або нові мітки до категорії, перейменовуйте її або створюйте власну. Файли, які не належать до жодної з ваших категорій, відображаються в розділі «Інше»." title = "Категорії бічної панелі" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Додати до робочої області" addToWorkspaceCount = "Додати {{count}} до робочої області" @@ -3918,7 +3971,6 @@ backToMyFiles = "Назад до «Мої файли»" breadcrumbs = "Шлях до папки" cancel = "Скасувати" classification = "Класифікація" -clearSearch = "Очистити пошук" clearSelection = "Очистити вибір" closeDetails = "Закрити відомості" create = "Створити" @@ -3950,6 +4002,7 @@ downloadAll = "Завантажити все" downloadVersion = "Завантажити цю версію" dropOverlay = "Перетягніть файли для завантаження" dropOverlaySub = "Файли спочатку потрапляють у Локальні. Використовуйте «Перемістити до» або «Зберегти в хмарі», щоб упорядкувати їх у папці." +duplicate = "Duplicate" file = "Файл" fileInfo = "File info" fileMenu = "Дії з файлом" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Змінити розмір дерева папок (кла save = "Зберегти" saveToServer = "Зберегти на сервері" saveToServerDisabledHint = "На цьому сервері збереження не ввімкнено. Попросіть адміністратора увімкнути його." -search = "Пошук" -searchPlaceholder = "Шукати в цій папці та підпапках" selectAll = "Вибрати все" selectAllHint = "Натисніть, щоб вибрати все. Підказка: утримуйте Ctrl (або Cmd), щоб додавати файли по одному, Shift — щоб вибрати діапазон." selectedCount = "Вибрано: {{count}}" @@ -4044,6 +4095,10 @@ title = "Ще немає хмарних файлів" hint = "Файли, збережені без завантаження, залишаються тут. Перетягніть файл, щоб додати його." title = "Немає локальних файлів" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Файли, які ви відкриєте або відредагуєте, з’являться тут." title = "Поки нічого не змінено" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Не вдалося видалити папку." deleteFolderFailedDetail = "Не вдалося видалити папку: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Не вдалося оновити вигляд папки." folderAppearanceFailedDetail = "Не вдалося оновити вигляд папки: {{message}}" moveFilesFailed = "Не вдалося перемістити файли." @@ -4113,6 +4170,11 @@ localHint = "Зберігається лише в цьому браузері" shared = "Спільний доступ" sharedHint = "Надано вам за посиланням" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Спочатку найстаріші" modifiedDesc = "Спочатку останні" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "У сховищі немає файлів. Спершу open = "Відкрити" openFile = "Відкрити файл" openFiles = "Відкрити файли" +selectFile = "Select {{name}}" selectFromStorage = "Вибрати зі сховища" upload = "Завантажити" uploadFile = "Завантажити файл" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Усе збережено" +analyzingFields = "Analysing form fields..." extractCsvError = "Не вдалося витягти CSV" extractXlsxError = "Не вдалося витягти XLSX" +filled = "filled" flattenAfterFilling = "Сплющити після заповнення" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "обов." requiredFieldsError = "Заповніть усі обов’язкові поля" rescanFields = "Повторно сканувати поля" @@ -4795,9 +4863,6 @@ title = "Зображення в PDF" [imageToPdf] tags = "конвертація,зображення,jpg,картинка,фото" -[infoBanner] -dismiss = "Закрити" - [invite] acceptError = "Не вдалося створити обліковий запис" accountFor = "Створення облікового запису для" @@ -4979,6 +5044,34 @@ uploadSuccess = "Завантаження успішне!" uploadSuccessMessage = "Ваші зображення передано." validating = "Перевірка сеансу..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Скануйте для завантаження фото. Зображення автоматично конвертуються в PDF." descriptionNoConvert = "Скануйте для завантаження фото зі свого мобільного пристрою." @@ -4997,8 +5090,13 @@ title = "Завантажити з мобільного" tags = "мультиінструмент,багатоопераційний,інтерфейс,перетягування,клієнтська частина,інтерактивний" title = "Мультиінструмент PDF" -[navbar] -search = "Пошук" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Автентифікація не відбулася. Можете закрити це вікно та спробувати ще раз." @@ -5127,6 +5225,7 @@ activeFiles = "Вигляд Активні файли пока allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Закрити" cropSettings = "Тепер, коли ми вибрали файл, який хочемо обрізати, можемо налаштувати інструмент Обрізка, щоб вибрати область, до якої слід обрізати PDF." +dialogLabel = "Onboarding" fileCheckbox = "Клацання по файлу позначає його для обробки. Можна вибрати кілька файлів для пакетних операцій." fileReplacement = "Змінений файл автоматично замінить оригінал у Робочій області, що дозволить легко пропустити його через інші інструменти." filesButton = "Кнопка Файли на панелі швидкого доступу дозволяє завантажувати PDF для роботи інструментів." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF в PDF/A" tags = "архів,довгостроковий,стандартний,конверсія,зберігання,консервація" title = "PDF в PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "друк,стандарт,конвертація,виробництво,додрукарська підготовка,архів" title = "PDF у PDF/X" @@ -6045,6 +6151,7 @@ small = "500 кредитів" xsmall = "100 кредитів" [plan.availablePlans] +currency = "Billing currency" subtitle = "Виберіть план, який відповідає вашим потребам" title = "Доступні плани" @@ -6624,6 +6731,7 @@ reachedTitle = "Досягнуто місячного ліміту витрат" title = "Не вдалося відкрити портал Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "з {{allowance}} безкоштовних PDF використано" capSuffix_other = "з {{allowance}} безкоштовних PDF використано" eyebrow = "Пробний період Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Надсилайте документ або сповіщення Email, коли політика запускається." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Розміщуйте оброблені документи в Nextcloud." @@ -7077,11 +7211,6 @@ title = "Офіційні SDK" beta = "Beta" deprecated = "Застаріло" -[portal.docs.search] -empty = "Немає відповідних docs" -placeholder = "Пошук docs" -results = "{{count}} результатів" - [portal.docs.skills] eyebrow = "SKILLS" lead = "Вбудовані іменовані можливості, які ваш агент викликає як один інструмент. Кожна навичка — це детермінований ланцюг операцій із прикріпленими оцінюваннями." @@ -7180,11 +7309,11 @@ editorAction = "Редактор" empty = "Жоден документ не відповідає цьому фільтру." rowActions = "Дії рядка" sensitiveLabel = "Конфіденційно" -sensitiveTitle = "Конфіденційно — потрібен доступ" [portal.documents.table.columns] action = "Конвеєр / Дія" document = "Документ" +labels = "Labels" product = "Продукт" status = "Статус" time = "Час" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Остання активність" region = "Регіон" status = "Статус" +target = "Target" version = "Версія" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "У цьому поданні сталася неочікуван retry = "Спробувати ще раз" title = "На цій сторінці щось пішло не так" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Назад" body = "Завантажте desktop app або self-host сервер." @@ -7380,11 +7560,6 @@ cancel = "Скасувати" confirm = "Відкликати ключ" title = "Відкликати API ключ" -[portal.infrastructure.attestationLabel] -attested = "Підтверджено" -inScope = "У межах обсягу" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Фільтрувати події аудиту за категорією" heading = "Журнали аудиту" @@ -7456,11 +7631,6 @@ info = "Інформація" success = "Успіх" warning = "Попередження" -[portal.infrastructure.certLabel] -certified = "Сертифіковано" -inProgress = "Виконується" -notStarted = "Не розпочато" - [portal.infrastructure.createKey] cancel = "Скасувати" createKey = "Створити ключ" @@ -7474,238 +7644,10 @@ subtitleCreated = "Скопіюйте цей секрет зараз — йог title = "Створити ключ API" titleCreated = "Ключ створено" -[portal.infrastructure.deployLabel] -live = "Працює" -queued = "У черзі" -rolledBack = "Відкочено" -rolling = "Розгортається" - -[portal.infrastructure.deployments] -msValue = "{{value}} мс" -throughputValue = "{{value}}/хв" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Розгорнув" -environment = "Середовище" -product = "Продукт" -status = "Статус" -version = "Версія" -when = "Коли" - -[portal.infrastructure.deployments.recent] -heading = "Останні розгортання" -subheading = "Останні випуски в продуктах і середовищах." - -[portal.infrastructure.deployments.regionColumns] -instances = "Екземпляри" -latency = "Затримка" -load = "Навантаження" -p99 = "P99" -region = "Регіон" -status = "Статус" -throughput = "Пропускна здатність" -uptime = "Час безвідмовної роботи" -version = "Версія" - -[portal.infrastructure.deployments.regions] -heading = "Регіони" -subheading = "Поточний стан кожного розгорнутого регіону Stirling — затримка, навантаження та версія випуску." - -[portal.infrastructure.deployments.regions.empty] -description = "Розгорнуті регіони з’являться тут після підготовки вашого робочого простору." -title = "Регіони не розгорнуто" - [portal.infrastructure.keyLabel] active = "Активний" revoked = "Відкликаний" -[portal.infrastructure.modelLabel] -active = "Активна" -degraded = "Погіршена" -disabled = "Вимкнена" - -[portal.infrastructure.models] -heading = "Моделі" -msValue = "{{value}} мс" -subheading = "Каталог моделей і маршрутизація, що забезпечують обробку документів у вашому робочому просторі." - -[portal.infrastructure.models.byom] -description = "Зареєструйте локальну або самостійно розміщену модель і закріпіть її за регіоном для обробки з урахуванням резидентності даних." -title = "Власна модель" - -[portal.infrastructure.models.catalogue] -heading = "Каталог" -sub = "Керовані моделі, доступні вашому робочому простору, з актуальною затримкою та вартістю." -subEnterprise = "Керовані, власні та локальні моделі — з доступним закріпленням за регіонами." - -[portal.infrastructure.models.catalogue.empty] -description = "Моделі в каталозі вашого робочого простору з’являться тут." -title = "Немає доступних моделей" - -[portal.infrastructure.models.columns] -cost = "Вартість" -latency = "Затримка" -load = "Навантаження" -model = "Модель" -status = "Статус" -type = "Тип" -version = "Версія" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/виклик" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Активні моделі" -avgLatency = "Середня затримка" -included = "Включено" -monthlySpend = "Щомісячні витрати на моделі" - -[portal.infrastructure.models.routing] -empty = "Правила маршрутизації не налаштовано." -heading = "Правила маршрутизації" -sub = "Яка модель обробляє кожну операцію. Типове правило застосовується, коли не збігається жодне вужче правило." -subLocked = "Спрямовуйте операції до конкретних моделей — доступно на платних планах." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Оновіться до Pro, щоб керувати тим, яка модель обробляє кожну операцію та тип документа." -title = "Маршрутизація моделей — платна функція" - -[portal.infrastructure.models.routingColumns] -default = "Типово" -docType = "Тип документа" -modelForAria = "Модель для {{operation}}" -operation = "Операція" -routedTo = "Спрямовано до" - -[portal.infrastructure.modelTypeLabel] -classification = "Класифікація" -extraction = "Видобування" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Погіршено" -down = "Недоступно" -healthy = "Справно" - -[portal.infrastructure.security.access.byok] -description = "Надайте ключ із власного KMS. Stirling шифрує ним, але все ще може читати." -label = "Власний ключ (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Ключі ніколи не залишають ваш KMS. Stirling зберігає лише шифротекст." -label = "Зберігайте власний ключ (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling керує ключами шифрування. Найпростіше — нуль операцій із ключами з вашого боку." -label = "Ключі, що зберігаються Stirling" - -[portal.infrastructure.security.accessPolicy] -heading = "Політика доступу до документів" -subheading = "Керує тим, хто може розшифровувати оброблені документи в стані спокою." - -[portal.infrastructure.security.attestations] -heading = "Підтвердження відповідності" -noReport = "Звіт недоступний" -subheading = "Стан аудиту за кожним фреймворком, зі звітами щодо підтверджених контролів." -viewReport = "Переглянути звіт →" - -[portal.infrastructure.security.compliance] -heading = "Відповідність вимогам" -subheading = "Підтвердження та сертифікації, що охоплюють платформу Stirling." - -[portal.infrastructure.security.empty] -description = "Конфігурація безпеки вашого робочого простору з’явиться тут." -title = "Стан безпеки недоступний" - -[portal.infrastructure.security.hyokBanner] -description = "З HYOK ключі шифрування ніколи не залишають ваш KMS. Stirling зберігає й обробляє лише шифротекст, який ви можете відкликати будь-коли." -title = "Stirling не може розшифрувати ваші документи" - -[portal.infrastructure.security.ipAllowlist] -empty = "Діапазони IP не налаштовано — дозволено всі IP." -heading = "Список дозволених IP" -sub = "Доступ до API обмежено цими діапазонами CIDR." -subLocked = "Обмежте доступ до API відомими діапазонами IP — доступно на платних планах." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Оновіться до Pro, щоб обмежити доступ до API конкретними мережами." -title = "Списки дозволених IP — платна функція" - -[portal.infrastructure.security.ipColumns] -added = "Додано" -addedBy = "Додав" -cidr = "CIDR" -label = "Мітка" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "Керування ключами шифрування" -keyId = "Ідентифікатор ключа" -lastRotated = "Остання ротація" -rotateKey = "Ротувати ключ" -rotationPolicy = "Політика ротації" -subheading = "Зберігання ключів, що шифрують документи в стані спокою — хто може розшифровувати та як ключі ротуються." - -[portal.infrastructure.security.managedBanner] -description = "Зберігання власних ключів BYOK і HYOK доступне на Enterprise. Оновіться, щоб надавати ключі з власного KMS." -title = "На вашому плані ключами керує Stirling" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Азійсько-Тихоокеанський регіон" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · межа даних GDPR" -label = "Європейський Союз" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Сполучені Штати" - -[portal.infrastructure.security.residencyHeader] -heading = "Резидентність даних" -subheading = "Де документи зберігаються та обробляються." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "{{value}} використано" - -[portal.infrastructure.storage.empty] -description = "Підключене сховище та використання з’являться тут." -title = "Сховище не налаштовано" - -[portal.infrastructure.storage.lifecycle] -active = "Активно" -activeRange = "0–{{value}} дн." -archived = "Архівовано" -coldStorage = "холодне сховище" -deleted = "Видалено" -never = "ніколи" -purged = "очищено" - -[portal.infrastructure.storage.providers] -connect = "Підключити" -connected = "Підключено" -heading = "Підключені провайдери" -subheading = "Куди записуються оброблені артефакти." - -[portal.infrastructure.storage.retention] -heading = "Зберігання" -subheading = "Як довго артефакти зберігаються перед видаленням за життєвим циклом." -windowLabel = "Типове вікно зберігання" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} день" -days_other = "{{count}} днів" -never = "Ніколи не видаляти" - -[portal.infrastructure.storage.totalUsage] -heading = "Загальне використання" -progressLabel = "Використано сховища" -subheading = "Сховище, використане всіма підключеними провайдерами." - [portal.infrastructure.tabs] apiKeys = "Ключі API" audit = "Журнали аудиту" @@ -7715,11 +7657,9 @@ security = "Безпека" storage = "Сховище" [portal.integrations] -addAnother = "Додати ще одну" availableHeading = "Доступні" comingSoonHeading = "Незабаром" connect = "Підключити" -connectedHeading = "Підключено" connectionCount_one = "{{count}} підключення" connectionCount_other = "{{count}} підключень" customApi = "Власний API" @@ -7737,6 +7677,10 @@ security = "Безпека" signing = "Підписування" storage = "Сховище" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Підключено" @@ -7767,7 +7711,6 @@ integrations = "Інтеграції" pipelines = "Конвеєри" policies = "Політики" procurement = "Закупівлі" -settings = "Налаштування" sources = "Джерела" usage = "Використання та оплата" users = "Користувачі" @@ -7784,7 +7727,7 @@ title = "Конвеєри" newPipeline = "Новий конвеєр" [portal.pipelines.builder] -addStep = "Додати інструмент" +activate = "Activate" back = "Назад до конвеєрів" cannotFollow = "Не можна прийняти {{produced}}" chooseAccount = "Виберіть обліковий запис" @@ -7792,28 +7735,38 @@ chooseDestination = "Виберіть призначення" chooseOperation = "Виберіть, що робить цей крок" chooseSource = "Виберіть джерело" discard = "Відхилити зміни" -enabled = "Увімкнено" inputs = "Вхідні дані" inputSource = "Вхідне джерело" inputTrigger = "Тригер" keepEditing = "Продовжити редагування" +moreActions = "More actions" needsConfiguring = "Потрібне налаштування" -needsUpload = "Потрібен завантажений файл" -noSources = "Поки що немає підключених джерел. Створіть одне, щоб використовувати його як вхід." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Жоден інструмент не відповідає вашому пошуку." -pipelineSettings = "Налаштування конвеєра" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Пошук інструментів" -selectToolBody = "Додайте інструмент, щоб створити конвеєр." -selectToolTitle = "Інструментів ще немає" sendToSystem = "Надіслати до іншої системи" stepsIncompatible = "Ці кроки не можуть працювати з тим, що створює попередній крок: {{tools}}." stepsNeedSetup = "Ці кроки все ще потрібно налаштувати перед збереженням: {{tools}}." -toolSettings = "Налаштування інструмента" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Нерозпізнана операція, залишено як є." unsavedBody = "У вас є незбережені зміни. Зберегти їх перед виходом чи відхилити?" unsavedTitle = "Незбережені зміни" -uploadUnsupported = "Завантажені файли поки не підтримуються в конвеєрах, тому ці кроки не можна зберегти: {{tools}}." usesDefaults = "Запускається з типовими налаштуваннями" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Об'єднує кожен файл із попереднього кроку" @@ -7825,31 +7778,36 @@ undeclared-operation = "Не вдається перевірити, що при [portal.pipelines.composer] addTool = "Додати інструмент" -cancel = "Скасувати" -chainEmpty = "Додайте інструмент, щоб почати створювати конвеєр." create = "Створити конвеєр" +createPaused = "Create paused" editingUnsupported = "Відображення цих параметрів інструмента для редагування поки не підтримується." -moveDown = "Перемістити вниз" -moveUp = "Перемістити вгору" +editSource = "Edit source" name = "Назва" namePlaceholder = "напр. Redaction sweep" noToolSettings = "Цей інструмент не має налаштовуваних параметрів." -operations_one = "Операція ({{count}})" -operations_other = "Операції ({{count}})" output = "Вивід" -removeStep = "Вилучити операцію" save = "Зберегти зміни" scheduleEvery = "Запускати кожні" -sources = "Джерела" -sourcesLoading = "Завантаження джерел..." trigger = "Тригер" triggerManual = "Лише вручну" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "днів" hours = "годин" minutes = "хвилин" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Видалити \"{{name}}\"? Цю дію не можна скасувати." cancel = "Скасувати" @@ -7867,6 +7825,37 @@ connectSource = "Підключити джерело" description = "Створіть свій перший конвеєр: виберіть джерела, для яких він запускається, з’єднайте операції та виберіть, куди надходитиме вивід." title = "Конвеєрів ще немає" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Активні" paused = "Призупинені" @@ -7879,7 +7868,6 @@ completed_one = "Запуск завершено." completed_other = "Усі {{count}} запусків завершено." empty = "Немає чого запускати: джерела не мали документів для обробки." failed = "Запуск не вдався: {{error}}" -historyCleared = "Історію очищено. Наступний запуск повторно обробить усе, що зараз є в джерелах." inFlight = "Немає нічого нового для запуску: документи все ще обробляються з попереднього запуску." parked_one = "Нічого запускати: {{count}} документ раніше не вдалося обробити, і його відкладено. Усуньте причину, а потім очистьте історію, щоб повторити спробу." parked_other = "Нічого запускати: {{count}} документів раніше не вдалося обробити, і їх відкладено. Усуньте причину, а потім очистьте історію, щоб повторити спробу." @@ -7895,6 +7883,7 @@ name = "Конвеєр" sources = "Джерела" status = "Статус" steps = "Кроки" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Відстеження папки" @@ -8209,6 +8198,24 @@ label = "Прикріпити до задачі Jira" description = "Надсилає оброблений документ як вкладення." label = "Надіслати документ email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Записує оброблений документ у папку." label = "Завантажити в Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Ліміт вичерпано" subtitle = "Ваш безкоштовний trial триває до {{date}}. Картка не потрібна." title = "Enterprise trial" -[portal.search] -ariaLabel = "Пошук" -placeholder = "Пошук у Stirling — endpoints, конвеєри, документи…" - -[portal.search.empty] -noActionsDescription = "Швидкі дії з’являться тут, коли стануть доступними." -noActionsTitle = "Немає швидких дій" -noMatches = "Немає збігів для «{{query}}»" -noMatchesDescription = "Спробуйте інше ключове слово або перегляньте каталог." - [portal.settings.groups] admin = "Адміністратор" @@ -8743,10 +8740,6 @@ cancel = "Скасувати" confirm = "Видалити" title = "Видалити джерело?" -[portal.sources.empty] -description = "Підключіть папку (а незабаром — і cloud storage), щоб ваші політики мали звідки отримувати документи." -title = "Джерела ще не підключено" - [portal.sources.kpi] inUse = "Використовується" total = "Підключення" @@ -8785,6 +8778,7 @@ unused = "Не використовується" documents = "Документи" source = "Джерело" status = "Статус" +type = "Type" usedBy = "Політики" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Політики та конфіденційність" [settings.preferences] title = "Параметри" -[settings.search] -placeholder = "Пошук сторінок налаштувань..." - [settings.security] description = "Оновіть свій пароль, щоб убезпечити обліковий запис." title = "Безпека" @@ -10128,6 +10119,7 @@ last = "Остання сторінка" maintainRatio = "Переключити збереження пропорцій" next = "Наступна сторінка" noSavedSigs = "Збережені підписи не знайдено" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Особисті підписи" previous = "Попередня сторінка" redo = "Повторити" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Не вдалося прибрати фон" hint = "Завантажте зображення підпису у форматі PNG або JPG" label = "Завантажити зображення підпису" placeholder = "Виберіть файл зображення" +previewAlt = "Current image signature" processing = "Обробка зображення..." removeBackground = "Прибрати білий фон (зробити прозорим)" @@ -10179,6 +10172,17 @@ saved = "Виберіть збережений підпис вище, потім text = "Після введення імені вище клацніть будь-де по PDF, щоб розмістити підпис." title = "Як додати підпис" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Перемістити підпис" pause = "Призупинити розміщення" @@ -10625,6 +10629,23 @@ title = "Завантажити на сервер" updateButton = "Оновити на сервері" uploadButton = "Завантажити на сервер" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Повернутися" pdfTools = "Інструменти PDF" placeholder = "Виберіть інструмент, щоб почати" premiumFeature = "Преміум-функція:" -searchTools = "Пошук інструментів" toolsHeader = "Інструменти" viewAllTools = "Переглянути всі інструменти" @@ -10822,6 +10842,9 @@ removal = "Видалення" signing = "Підписання" verification = "Перевірка" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Інструментів не знайдено" noTools = "Немає доступних інструментів" @@ -10979,6 +11002,13 @@ approver = "Схвалює політику" editor = "Редактор" processor = "Обробник" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Скасувати запрошення для {{email}}? Вони не зможуть приєднатися за поточним посиланням." cancelInviteTitle = "Скасувати запрошення" @@ -10998,10 +11028,8 @@ title = "Учасників ще немає" addToTeam = "Додати до команди" guestCount = "{{count}} гість" guests = "Гості" -guestsDesc = "Зовнішні співробітники, обмежені тим, чим ви поділилися. Лише Редактор." ledBy = "під керівництвом {{owner}}" org = "Організація" -orgDesc = "Власники з повноваженнями на рівні всієї організації та схваленням політик" owners = "{{count}} власник" team = "Команда {{name}}" teamMeta = "{{count}} людей" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Запросив(ла) {{who}}" cancel = "Скасувати" -count = "{{count}} очікують" -desc = "Запрошені люди, які ще не приєдналися. Вони займають місце, доки не приймуть запрошення." expiresInDays_one = "Закінчується через {{count}} день" expiresInDays_other = "Закінчується через {{count}} днів" expiresToday = "Закінчується сьогодні" title = "Запрошення, що очікують" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Сталася помилка під час звернення до бекенду, або у вас немає доступу. Спробуйте ще раз." title = "Не вдалося завантажити учасників" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Пошук вкладень" title = "Вкладення" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Назва закладки" bookmarkTitleRequired = "Потрібно вказати назву закладки" closeSidebar = "Закрити бічну панель закладок" collapseAll = "Згорнути всі закладки" +empty = "No bookmarks in this document" expandAll = "Розгорнути всі закладки" searchPlaceholder = "Пошук закладок" +title = "Bookmarks" [viewer.comments] addComment = "Додати коментар" @@ -11264,10 +11297,12 @@ locateAnnotation = "Знайти в документі" moreActions = "Додаткові дії" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Сторінка {{page}}" placingHint = "Клацніть сторінку, щоб розмістити… (скасувати)" removeCommentOnly = "Прибрати лише коментар" saveReply = "Зберегти відповідь" +searchPlaceholder = "Search comments" title = "Коментарі" typeComment = "Коментар" typeInsertText = "Вставити текст" @@ -11290,6 +11325,7 @@ unsavedDesc = "У вас є незбережені зміни" closeSidebar = "Закрити бічну панель шарів" hideAll = "Приховати всі шари" showAll = "Показати всі шари" +title = "Layers" [viewer.link] delete = "Видалити посилання" @@ -11646,6 +11682,7 @@ exitRedaction = "Вийти з режиму затемнення" exportAll = "Експортувати PDF" exportSelected = "Експортувати вибрані сторінки" formFill = "Заповнити форму" +hideToolbar = "Hide toolbar" multiTool = "Мультиінструмент" panMode = "Режим переміщення" print = "Надрукувати PDF" @@ -11666,6 +11703,7 @@ selectAll = "Вибрати все" selectByNumber = "Вибрати за номерами сторінок" selectLanguage = "Вибрати мову" share = "Надати спільний доступ" +showToolbar = "Show toolbar" toggleAnnotations = "Перемкнути видимість анотацій" toggleAttachments = "Перемкнути вкладення" toggleBookmarks = "Перемкнути закладки" diff --git a/frontend/editor/public/locales/vi-VN/translation.toml b/frontend/editor/public/locales/vi-VN/translation.toml index ebdc5b3615..4ff67c254b 100644 --- a/frontend/editor/public/locales/vi-VN/translation.toml +++ b/frontend/editor/public/locales/vi-VN/translation.toml @@ -1,7 +1,6 @@ alphabet = "Bảng chữ cái" apply = "Áp dụng" applyAndContinue = "Lưu và rời đi" -areYouSure = "Bạn có chắc muốn rời đi không?" back = "Quay lại" black = "Đen" blue = "Xanh dương" @@ -106,6 +105,7 @@ undoSuccess = "Hoàn tác thành công" unknown = "Không xác định" unpin = "Bỏ ghim tệp (thay thế sau khi chạy công cụ)" unsavedChanges = "Bạn có thay đổi chưa lưu đối với PDF." +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "Thay đổi chưa lưu" unsupported = "Không được hỗ trợ" username = "Tên người dùng" @@ -1879,6 +1879,9 @@ width = "Độ rộng" [app] description = "Giải pháp thay thế Adobe Acrobat miễn phí (10M+ lượt tải xuống)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "Chuyển sang PDF/A-3b" convertToPdfA3bDescription = "Tạo PDF lưu trữ với tệp đính kèm được nhúng" @@ -2933,12 +2936,16 @@ text = "Để các quyền này không bị thay đổi, hãy dùng công cụ T [chat.actions] copy = "Sao chép tin nhắn" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Tùy chọn tác nhân Stirling" +assistant = "AI Assistant" clearChat = "Xóa trò chuyện" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "Thao tác này sẽ sử dụng tín dụng đám mây của bạn" tooltip = "Lấy màu từ màn hình" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "Chọn màu" [common] +actions = "Actions" back = "Quay lại" cancel = "Hủy" close = "Đóng" +codeSample = "Code sample" collapse = "Thu gọn" confirm = "Xác nhận" continue = "Tiếp tục" copied = "Đã sao chép!" copy = "Sao chép" done = "Xong" +enterValue = "Enter value" error = "Lỗi" expand = "Mở rộng" loading = "Đang tải..." next = "Tiếp" +open = "Open" preview = "Xem trước" previous = "Trước" refresh = "Làm mới" remaining = "Còn lại" retry = "Thử lại" save = "Lưu" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "Xóa lựa chọn" @@ -3185,6 +3199,7 @@ title = "Phương pháp nén" [compress.settings] desiredSize = "Kích thước tệp mong muốn" desiredSizePlaceholder = "Nhập kích thước" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "Nén là cách đơn giản để giảm kích thước tệp. Chọn Kích thước tệp để nhập kích thước mục tiêu và chúng tôi sẽ điều chỉnh chất lượng cho bạn. Chọn Chất lượng để tự đặt mức nén thủ công." @@ -3387,6 +3402,24 @@ pdfOptions = "Tùy chọn PDF" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "Chọn định dạng nguồn trước" settings = "Cài đặt" single = "Một tệp" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "Tìm kiếm..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "Sửa" @@ -3824,11 +3861,13 @@ shareSelected = "Chia sẻ mục đã chọn" sharing = "Chia sẻ" showAll = "Hiện tất cả" showHistory = "Hiện lịch sử" +sortBy = "Sort files" sortByDate = "Sắp xếp theo ngày" sortByName = "Sắp xếp theo tên" sortBySize = "Sắp xếp theo kích thước" storage = "Lưu trữ" storageState = "Lưu trữ" +storageUsed = "Storage used" synced = "Đã đồng bộ" title = "Tải tệp PDF lên" toolChain = "Công cụ đã áp dụng" @@ -3874,8 +3913,12 @@ addFiles = "Thêm tệp" addingFiles = "Đang thêm tệp…" collapse = "Thu gọn thanh bên" customizeGroups = "Tùy chỉnh nhóm" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "Mở tệp để bắt đầu" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "Mở rộng thanh bên" googleDrive = "Google Drive" googleDriveDisabled = "Google Drive chưa được cấu hình" @@ -3888,15 +3931,17 @@ openFromComputer = "Mở từ máy tính" openSettings = "Mở cài đặt" other = "Khác" recent = "Gần đây" -search = "Tìm kiếm" -searchPlaceholder = "Tìm kiếm tệp..." viewAll = "Xem tất cả {{count}} tệp" [fileSidebar.fileItem] closeViewer = "Đóng trình xem" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "Mở trong trình xem" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "Đặt lại về mặc định" subtitle = "Nhóm các tệp của bạn vào danh mục cha. Thêm nhãn hiện có hoặc nhãn mới vào danh mục, đổi tên danh mục hoặc tự tạo danh mục riêng. Các tệp không thuộc danh mục nào của bạn sẽ xuất hiện trong “Khác”." title = "Danh mục thanh bên" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "Thêm vào không gian làm việc" addToWorkspaceCount = "Thêm {{count}} vào không gian làm việc" @@ -3918,7 +3971,6 @@ backToMyFiles = "Quay lại Tệp của tôi" breadcrumbs = "Đường dẫn thư mục" cancel = "Hủy" classification = "Phân loại" -clearSearch = "Xóa tìm kiếm" clearSelection = "Bỏ chọn" closeDetails = "Đóng chi tiết" create = "Tạo" @@ -3950,6 +4002,7 @@ downloadAll = "Tải xuống tất cả" downloadVersion = "Tải xuống phiên bản này" dropOverlay = "Thả tệp để tải lên" dropOverlaySub = "Các tệp bắt đầu ở Cục bộ. Dùng 'Di chuyển đến' hoặc 'Lưu lên đám mây' để sắp xếp vào thư mục." +duplicate = "Duplicate" file = "Tệp" fileInfo = "File info" fileMenu = "Hành động tệp" @@ -3983,8 +4036,6 @@ resizeFolderTree = "Thay đổi kích thước cây thư mục (phím mũi tên, save = "Lưu" saveToServer = "Lưu lên máy chủ" saveToServerDisabledHint = "Lưu lên máy chủ chưa được bật trên máy chủ này. Hãy yêu cầu quản trị viên bật nó." -search = "Tìm kiếm" -searchPlaceholder = "Tìm trong thư mục này và các thư mục con" selectAll = "Chọn tất cả" selectAllHint = "Nhấn để chọn tất cả. Mẹo: giữ Ctrl (hoặc Cmd) để thêm từng tệp, Shift để chọn một dải." selectedCount = "Đã chọn {{count}}" @@ -4044,6 +4095,10 @@ title = "Chưa có tệp đám mây" hint = "Các tệp lưu mà không tải lên sẽ ở đây. Thả một tệp để thêm." title = "Không có tệp chỉ cục bộ" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "Các tệp bạn mở hoặc chỉnh sửa sẽ xuất hiện ở đây." title = "Chưa có gì được sửa đổi" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "Không thể xóa thư mục." deleteFolderFailedDetail = "Không thể xóa thư mục: {{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "Không thể cập nhật giao diện thư mục." folderAppearanceFailedDetail = "Không thể cập nhật giao diện thư mục: {{message}}" moveFilesFailed = "Không thể di chuyển tệp." @@ -4113,6 +4170,11 @@ localHint = "Chỉ lưu trong trình duyệt này" shared = "Được chia sẻ" sharedHint = "Được chia sẻ với bạn qua liên kết" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "Cũ nhất trước" modifiedDesc = "Gần đây trước" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "Không có tệp trong bộ nhớ. Hãy mở một số open = "Mở" openFile = "Mở tệp" openFiles = "Mở nhiều tệp" +selectFile = "Select {{name}}" selectFromStorage = "Chọn từ Lưu trữ" upload = "Tải lên" uploadFile = "Tải tệp lên" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "Đã lưu tất cả" +analyzingFields = "Analysing form fields..." extractCsvError = "Không thể trích xuất CSV" extractXlsxError = "Không thể trích xuất XLSX" +filled = "filled" flattenAfterFilling = "Làm phẳng sau khi điền" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "bắt buộc" requiredFieldsError = "Vui lòng điền tất cả các trường bắt buộc" rescanFields = "Quét lại trường" @@ -4795,9 +4863,6 @@ title = "Hình ảnh sang PDF" [imageToPdf] tags = "chuyển đổi,img,jpg,hình ảnh,ảnh" -[infoBanner] -dismiss = "Bỏ qua" - [invite] acceptError = "Không thể tạo tài khoản" accountFor = "Đang tạo tài khoản cho" @@ -4979,6 +5044,34 @@ uploadSuccess = "Tải lên thành công!" uploadSuccessMessage = "Hình ảnh của bạn đã được chuyển." validating = "Đang xác thực phiên..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "Quét để tải ảnh. Hình ảnh tự động chuyển sang PDF." descriptionNoConvert = "Quét để tải ảnh từ thiết bị di động của bạn." @@ -4997,8 +5090,13 @@ title = "Tải lên từ Di động" tags = "Đa công cụ,Đa thao tác,Giao diện người dùng,nhấp kéo,phía trước,phía máy khách,tương tác,có thể tương tác,di chuyển" title = "Công cụ đa năng PDF" -[navbar] -search = "Tìm kiếm" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "Xác thực không thành công. Bạn có thể đóng cửa sổ này và thử lại." @@ -5127,6 +5225,7 @@ activeFiles = "Chế độ xem Tệp đang hoạt động hiể allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "Đóng" cropSettings = "Bây giờ chúng ta đã chọn tệp muốn cắt, có thể cấu hình công cụ Cắt xén để chọn vùng cần cắt PDF." +dialogLabel = "Onboarding" fileCheckbox = "Nhấp vào một tệp sẽ chọn nó để xử lý. Bạn có thể chọn nhiều tệp cho các thao tác hàng loạt." fileReplacement = "Tệp đã chỉnh sửa sẽ tự động thay thế tệp gốc trong Bàn làm việc, giúp bạn dễ dàng chạy thêm các công cụ khác." filesButton = "Nút Tệp trên thanh Truy cập nhanh cho phép bạn tải PDF lên để dùng các công cụ." @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF sang PDF/A" tags = "lưu trữ,dài hạn,tiêu chuẩn,chuyển đổi,lưu trữ,bảo quản" title = "PDF sang PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "in ấn,tiêu chuẩn,chuyển đổi,sản xuất,tiền in,lưu trữ" title = "PDF sang PDF/X" @@ -6045,6 +6151,7 @@ small = "500 Tín dụng" xsmall = "100 Tín dụng" [plan.availablePlans] +currency = "Billing currency" subtitle = "Chọn gói phù hợp với nhu cầu của bạn" title = "Các gói khả dụng" @@ -6624,6 +6731,7 @@ reachedTitle = "Đã đạt giới hạn chi tiêu hàng tháng" title = "Không thể mở portal Stripe" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "trong {{allowance}} PDF miễn phí đã dùng" capSuffix_other = "trong {{allowance}} PDF miễn phí đã dùng" eyebrow = "Dùng thử Processor" @@ -6873,6 +6981,32 @@ label = "Jira" description = "Gửi email tài liệu hoặc thông báo khi một chính sách chạy." label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "Lưu tài liệu đã xử lý vào Nextcloud." @@ -7077,11 +7211,6 @@ title = "SDK chính thức" beta = "Beta" deprecated = "Không còn dùng" -[portal.docs.search] -empty = "Không có tài liệu phù hợp" -placeholder = "Tìm kiếm tài liệu" -results = "{{count}} kết quả" - [portal.docs.skills] eyebrow = "KỸ NĂNG" lead = "Các năng lực đi kèm, có tên, mà agent của bạn gọi như một công cụ duy nhất. Mỗi kỹ năng là một chuỗi thao tác xác định với đánh giá đính kèm." @@ -7180,11 +7309,11 @@ editorAction = "Trình chỉnh sửa" empty = "Không có tài liệu nào khớp với bộ lọc này." rowActions = "Hành động hàng" sensitiveLabel = "Nhạy cảm" -sensitiveTitle = "Nhạy cảm — yêu cầu quyền truy cập" [portal.documents.table.columns] action = "Pipeline / Hành động" document = "Tài liệu" +labels = "Labels" product = "Sản phẩm" status = "Trạng thái" time = "Thời gian" @@ -7200,6 +7329,7 @@ host = "Host" lastSeen = "Lần thấy gần nhất" region = "Khu vực" status = "Trạng thái" +target = "Target" version = "Phiên bản" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "Chế độ xem này gặp lỗi không mong muốn. Hãy thử l retry = "Thử lại" title = "Đã xảy ra lỗi trên trang này" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "Quay lại" body = "Tải xuống ứng dụng desktop hoặc tự lưu trữ máy chủ." @@ -7380,11 +7560,6 @@ cancel = "Hủy" confirm = "Thu hồi khóa" title = "Thu hồi API key" -[portal.infrastructure.attestationLabel] -attested = "Đã chứng thực" -inScope = "Trong phạm vi" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "Lọc sự kiện kiểm tra theo danh mục" heading = "Nhật ký kiểm tra" @@ -7456,11 +7631,6 @@ info = "Thông tin" success = "Thành công" warning = "Cảnh báo" -[portal.infrastructure.certLabel] -certified = "Đã chứng nhận" -inProgress = "Đang tiến hành" -notStarted = "Chưa bắt đầu" - [portal.infrastructure.createKey] cancel = "Hủy" createKey = "Tạo khóa" @@ -7474,238 +7644,10 @@ subtitleCreated = "Sao chép bí mật này ngay — nó sẽ không được hi title = "Tạo khóa API" titleCreated = "Đã tạo khóa" -[portal.infrastructure.deployLabel] -live = "Đang chạy" -queued = "Đang xếp hàng" -rolledBack = "Đã rollback" -rolling = "Đang triển khai" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/phút" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "Được triển khai bởi" -environment = "Môi trường" -product = "Sản phẩm" -status = "Trạng thái" -version = "Phiên bản" -when = "Thời điểm" - -[portal.infrastructure.deployments.recent] -heading = "Triển khai gần đây" -subheading = "Các đợt phát hành mới nhất trên các sản phẩm và môi trường." - -[portal.infrastructure.deployments.regionColumns] -instances = "Phiên bản" -latency = "Độ trễ" -load = "Tải" -p99 = "P99" -region = "Khu vực" -status = "Trạng thái" -throughput = "Thông lượng" -uptime = "Thời gian hoạt động" -version = "Phiên bản" - -[portal.infrastructure.deployments.regions] -heading = "Khu vực" -subheading = "Tình trạng trực tiếp cho từng khu vực Stirling đã triển khai — độ trễ, tải và phiên bản phát hành." - -[portal.infrastructure.deployments.regions.empty] -description = "Các khu vực đã triển khai sẽ xuất hiện tại đây sau khi workspace của bạn được cấp phát." -title = "Chưa triển khai khu vực nào" - [portal.infrastructure.keyLabel] active = "Đang hoạt động" revoked = "Đã thu hồi" -[portal.infrastructure.modelLabel] -active = "Đang hoạt động" -degraded = "Suy giảm" -disabled = "Đã tắt" - -[portal.infrastructure.models] -heading = "Mô hình" -msValue = "{{value}} ms" -subheading = "Danh mục mô hình và định tuyến hỗ trợ xử lý tài liệu trên toàn workspace của bạn." - -[portal.infrastructure.models.byom] -description = "Đăng ký một mô hình on-prem hoặc self-hosted và ghim nó vào một khu vực để xử lý theo ràng buộc lưu trú dữ liệu." -title = "Mang mô hình của riêng bạn" - -[portal.infrastructure.models.catalogue] -heading = "Danh mục" -sub = "Các mô hình được quản lý khả dụng cho workspace của bạn, với độ trễ và chi phí trực tiếp." -subEnterprise = "Các mô hình được quản lý, mang theo của riêng bạn và on-prem — có thể ghim theo từng khu vực." - -[portal.infrastructure.models.catalogue.empty] -description = "Các mô hình trong danh mục của workspace sẽ xuất hiện tại đây." -title = "Không có mô hình khả dụng" - -[portal.infrastructure.models.columns] -cost = "Chi phí" -latency = "Độ trễ" -load = "Tải" -model = "Mô hình" -status = "Trạng thái" -type = "Loại" -version = "Phiên bản" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/lần gọi" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "Mô hình đang hoạt động" -avgLatency = "Độ trễ trung bình" -included = "Đã bao gồm" -monthlySpend = "Chi tiêu mô hình hàng tháng" - -[portal.infrastructure.models.routing] -empty = "Chưa cấu hình quy tắc định tuyến nào." -heading = "Quy tắc định tuyến" -sub = "Mô hình nào xử lý từng thao tác. Mặc định áp dụng khi không có quy tắc hẹp hơn khớp." -subLocked = "Định tuyến thao tác đến các mô hình cụ thể — khả dụng trên gói trả phí." - -[portal.infrastructure.models.routing.lockedBanner] -description = "Nâng cấp lên Pro để kiểm soát mô hình nào xử lý từng thao tác và loại tài liệu." -title = "Định tuyến mô hình là tính năng trả phí" - -[portal.infrastructure.models.routingColumns] -default = "Mặc định" -docType = "Loại tài liệu" -modelForAria = "Mô hình cho {{operation}}" -operation = "Thao tác" -routedTo = "Định tuyến đến" - -[portal.infrastructure.modelTypeLabel] -classification = "Phân loại" -extraction = "Trích xuất" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "Suy giảm" -down = "Ngừng hoạt động" -healthy = "Khỏe mạnh" - -[portal.infrastructure.security.access.byok] -description = "Cung cấp khóa từ KMS của riêng bạn. Stirling mã hóa bằng khóa đó nhưng vẫn có thể đọc." -label = "Mang khóa của riêng bạn (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "Khóa không bao giờ rời khỏi KMS của bạn. Stirling chỉ giữ ciphertext." -label = "Giữ khóa của riêng bạn (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling quản lý khóa mã hóa. Đơn giản nhất — phía bạn không cần vận hành khóa." -label = "Khóa do Stirling giữ" - -[portal.infrastructure.security.accessPolicy] -heading = "Chính sách truy cập tài liệu" -subheading = "Kiểm soát ai có thể giải mã tài liệu đã xử lý khi lưu trữ." - -[portal.infrastructure.security.attestations] -heading = "Chứng nhận tuân thủ" -noReport = "Không có báo cáo" -subheading = "Tư thế kiểm tra theo từng framework, với báo cáo khả dụng cho các kiểm soát đã được chứng nhận." -viewReport = "Xem báo cáo →" - -[portal.infrastructure.security.compliance] -heading = "Tuân thủ" -subheading = "Chứng nhận và chứng chỉ bao phủ nền tảng Stirling." - -[portal.infrastructure.security.empty] -description = "Cấu hình bảo mật của workspace sẽ xuất hiện tại đây." -title = "Không có tư thế bảo mật" - -[portal.infrastructure.security.hyokBanner] -description = "Với HYOK, khóa mã hóa không bao giờ rời khỏi KMS của bạn. Stirling chỉ lưu trữ và xử lý ciphertext mà bạn có thể thu hồi bất cứ lúc nào." -title = "Stirling không thể giải mã tài liệu của bạn" - -[portal.infrastructure.security.ipAllowlist] -empty = "Chưa cấu hình dải IP nào — tất cả IP đều được phép." -heading = "Danh sách cho phép IP" -sub = "Quyền truy cập API bị giới hạn trong các dải CIDR này." -subLocked = "Giới hạn quyền truy cập API vào các dải IP đã biết — khả dụng trên gói trả phí." - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "Nâng cấp lên Pro để giới hạn quyền truy cập API vào các mạng cụ thể." -title = "Danh sách cho phép IP là tính năng trả phí" - -[portal.infrastructure.security.ipColumns] -added = "Đã thêm" -addedBy = "Được thêm bởi" -cidr = "CIDR" -label = "Nhãn" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "Quản lý khóa mã hóa" -keyId = "Mã định danh khóa" -lastRotated = "Lần xoay vòng gần nhất" -rotateKey = "Xoay vòng khóa" -rotationPolicy = "Chính sách xoay vòng" -subheading = "Quyền lưu giữ các khóa mã hóa tài liệu khi lưu trữ — ai có thể giải mã và cách khóa được xoay vòng." - -[portal.infrastructure.security.managedBanner] -description = "Quyền lưu giữ bring-your-own-key (BYOK) và hold-your-own-key (HYOK) khả dụng trên Enterprise. Nâng cấp để cung cấp khóa từ KMS của riêng bạn." -title = "Khóa được Stirling quản lý trên gói của bạn" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "Châu Á - Thái Bình Dương" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · ranh giới dữ liệu GDPR" -label = "Liên minh Châu Âu" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "Hoa Kỳ" - -[portal.infrastructure.security.residencyHeader] -heading = "Lưu trú dữ liệu" -subheading = "Nơi tài liệu được lưu trữ và xử lý." - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "Đã dùng {{value}}" - -[portal.infrastructure.storage.empty] -description = "Lưu trữ đã kết nối và mức sử dụng sẽ xuất hiện tại đây." -title = "Chưa cấu hình lưu trữ" - -[portal.infrastructure.storage.lifecycle] -active = "Đang hoạt động" -activeRange = "0–{{value}} ngày" -archived = "Đã lưu trữ" -coldStorage = "lưu trữ lạnh" -deleted = "Đã xóa" -never = "không bao giờ" -purged = "đã xóa vĩnh viễn" - -[portal.infrastructure.storage.providers] -connect = "Kết nối" -connected = "Đã kết nối" -heading = "Nhà cung cấp đã kết nối" -subheading = "Nơi các artifact đã xử lý được ghi." - -[portal.infrastructure.storage.retention] -heading = "Lưu giữ" -subheading = "Thời gian giữ artifact trước khi xóa theo vòng đời." -windowLabel = "Khoảng thời gian lưu giữ mặc định" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} ngày" -days_other = "{{count}} ngày" -never = "Không bao giờ xóa" - -[portal.infrastructure.storage.totalUsage] -heading = "Tổng mức sử dụng" -progressLabel = "Dung lượng đã dùng" -subheading = "Dung lượng đã tiêu thụ trên tất cả nhà cung cấp đã kết nối." - [portal.infrastructure.tabs] apiKeys = "Khóa API" audit = "Nhật ký kiểm tra" @@ -7715,11 +7657,9 @@ security = "Bảo mật" storage = "Lưu trữ" [portal.integrations] -addAnother = "Thêm mục khác" availableHeading = "Có sẵn" comingSoonHeading = "Sắp ra mắt" connect = "Kết nối" -connectedHeading = "Đã kết nối" connectionCount_one = "{{count}} kết nối" connectionCount_other = "{{count}} kết nối" customApi = "API tùy chỉnh" @@ -7737,6 +7677,10 @@ security = "Bảo mật" signing = "Ký" storage = "Lưu trữ" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "Đã kết nối" @@ -7767,7 +7711,6 @@ integrations = "Tích hợp" pipelines = "Pipeline" policies = "Chính sách" procurement = "Mua sắm" -settings = "Cài đặt" sources = "Nguồn" usage = "Sử dụng & Thanh toán" users = "Người dùng" @@ -7784,7 +7727,7 @@ title = "Pipeline" newPipeline = "Pipeline mới" [portal.pipelines.builder] -addStep = "Thêm công cụ" +activate = "Activate" back = "Quay lại pipeline" cannotFollow = "Không thể nhận {{produced}}" chooseAccount = "Chọn tài khoản" @@ -7792,28 +7735,38 @@ chooseDestination = "Chọn đích đến" chooseOperation = "Chọn việc bước này thực hiện" chooseSource = "Chọn nguồn" discard = "Hủy thay đổi" -enabled = "Đã bật" inputs = "Đầu vào" inputSource = "Nguồn đầu vào" inputTrigger = "Kích hoạt" keepEditing = "Tiếp tục chỉnh sửa" +moreActions = "More actions" needsConfiguring = "Cần thiết lập" -needsUpload = "Cần một file đã upload" -noSources = "Chưa có nguồn nào được kết nối. Tạo một nguồn để dùng làm đầu vào." +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "Không có công cụ nào khớp với tìm kiếm của bạn." -pipelineSettings = "Cài đặt pipeline" +pause = "Pause" +rename = "Rename pipeline" searchTools = "Tìm kiếm công cụ" -selectToolBody = "Thêm công cụ để xây dựng pipeline của bạn." -selectToolTitle = "Chưa có công cụ" sendToSystem = "Gửi đến hệ thống khác" stepsIncompatible = "Các bước này không thể chạy trên kết quả mà bước trước tạo ra: {{tools}}." stepsNeedSetup = "Các bước này vẫn cần thiết lập trước khi lưu: {{tools}}." -toolSettings = "Cài đặt công cụ" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "Thao tác không được nhận dạng, giữ nguyên." unsavedBody = "Bạn có thay đổi chưa lưu. Lưu trước khi rời đi hay hủy chúng?" unsavedTitle = "Thay đổi chưa lưu" -uploadUnsupported = "File đã upload chưa được hỗ trợ trong pipeline, vì vậy không thể lưu các bước này: {{tools}}." usesDefaults = "Chạy với cài đặt mặc định" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "Kết hợp mọi file từ bước trước" @@ -7825,31 +7778,36 @@ undeclared-operation = "Không thể kiểm tra bước này chấp nhận gì" [portal.pipelines.composer] addTool = "Thêm công cụ" -cancel = "Hủy" -chainEmpty = "Thêm công cụ để bắt đầu xây dựng pipeline của bạn." create = "Tạo pipeline" +createPaused = "Create paused" editingUnsupported = "Chưa hỗ trợ hiển thị các tham số công cụ này để chỉnh sửa." -moveDown = "Di chuyển xuống" -moveUp = "Di chuyển lên" +editSource = "Edit source" name = "Tên" namePlaceholder = "ví dụ: Quét che giấu" noToolSettings = "Công cụ này không có cài đặt có thể cấu hình." -operations_one = "Thao tác ({{count}})" -operations_other = "Thao tác ({{count}})" output = "Đầu ra" -removeStep = "Xóa thao tác" save = "Lưu thay đổi" scheduleEvery = "Chạy mỗi" -sources = "Nguồn" -sourcesLoading = "Đang tải nguồn..." trigger = "Trigger" triggerManual = "Chỉ thủ công" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "ngày" hours = "giờ" minutes = "phút" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "Xóa \"{{name}}\"? Không thể hoàn tác thao tác này." cancel = "Hủy" @@ -7867,6 +7825,37 @@ connectSource = "Kết nối một nguồn" description = "Tạo pipeline đầu tiên của bạn: chọn các nguồn mà nó chạy, nối chuỗi các thao tác và chọn nơi đầu ra sẽ đến." title = "Chưa có pipeline" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "Đang hoạt động" paused = "Đã tạm dừng" @@ -7879,7 +7868,6 @@ completed_one = "Lượt chạy đã hoàn tất." completed_other = "Tất cả {{count}} lượt chạy đã hoàn tất." empty = "Không có gì để chạy: các nguồn không có tài liệu để xử lý." failed = "Lượt chạy thất bại: {{error}}" -historyCleared = "Đã xóa lịch sử. Lần chạy tiếp theo sẽ xử lý lại mọi thứ hiện có trong các nguồn." inFlight = "Không có gì mới để chạy: tài liệu vẫn đang được xử lý từ một lần chạy trước đó." parked_one = "Không có gì để chạy: {{count}} tài liệu đã thất bại trước đó và đang được tạm dừng. Khắc phục nguyên nhân, rồi xóa lịch sử để thử lại." parked_other = "Không có gì để chạy: {{count}} tài liệu đã thất bại trước đó và đang được tạm dừng. Khắc phục nguyên nhân, rồi xóa lịch sử để thử lại." @@ -7895,6 +7883,7 @@ name = "Pipeline" sources = "Nguồn" status = "Trạng thái" steps = "Bước" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "Theo dõi thư mục" @@ -8209,6 +8198,24 @@ label = "Đính kèm vào Jira issue" description = "Gửi tài liệu đã xử lý dưới dạng tệp đính kèm." label = "Gửi tài liệu qua email (Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "Ghi tài liệu đã xử lý vào một thư mục." label = "Tải lên Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "Đã đạt giới hạn" subtitle = "Bản dùng thử miễn phí của bạn kéo dài đến {{date}}. Không cần thẻ." title = "Dùng thử Enterprise" -[portal.search] -ariaLabel = "Tìm kiếm" -placeholder = "Tìm kiếm Stirling — endpoint, pipeline, tài liệu…" - -[portal.search.empty] -noActionsDescription = "Hành động nhanh sẽ xuất hiện ở đây khi có sẵn." -noActionsTitle = "Không có hành động nhanh" -noMatches = "Không có kết quả cho \"{{query}}\"" -noMatchesDescription = "Thử từ khóa khác hoặc duyệt catalogue." - [portal.settings.groups] admin = "Quản trị" @@ -8743,10 +8740,6 @@ cancel = "Hủy" confirm = "Xóa" title = "Xóa nguồn?" -[portal.sources.empty] -description = "Kết nối một thư mục (và sắp tới là cloud storage) để chính sách của bạn có nơi lấy tài liệu." -title = "Chưa kết nối nguồn nào" - [portal.sources.kpi] inUse = "Đang sử dụng" total = "Kết nối" @@ -8785,6 +8778,7 @@ unused = "Chưa dùng" documents = "Tài liệu" source = "Nguồn" status = "Trạng thái" +type = "Type" usedBy = "Chính sách" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "Chính sách & Quyền riêng tư" [settings.preferences] title = "Tùy chọn" -[settings.search] -placeholder = "Tìm kiếm trang cài đặt..." - [settings.security] description = "Cập nhật mật khẩu để giữ an toàn cho tài khoản của bạn." title = "Bảo mật" @@ -10128,6 +10119,7 @@ last = "Trang cuối" maintainRatio = "Bật/tắt giữ tỉ lệ khung hình" next = "Trang tiếp" noSavedSigs = "Không tìm thấy chữ ký đã lưu" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "Chữ ký cá nhân" previous = "Trang trước" redo = "Làm lại" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "Xóa nền thất bại" hint = "Tải lên ảnh chữ ký định dạng PNG hoặc JPG" label = "Tải lên ảnh chữ ký" placeholder = "Chọn tệp hình ảnh" +previewAlt = "Current image signature" processing = "Đang xử lý hình ảnh..." removeBackground = "Xóa nền trắng (tạo trong suốt)" @@ -10179,6 +10172,17 @@ saved = "Chọn một chữ ký đã lưu ở trên, rồi nhấp vào bất k text = "Sau khi nhập tên ở trên, bấm vào bất kỳ đâu trên PDF để đặt chữ ký của bạn." title = "Cách thêm chữ ký" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "Di chuyển chữ ký" pause = "Tạm dừng đặt" @@ -10625,6 +10629,23 @@ title = "Tải lên máy chủ" updateButton = "Cập nhật trên máy chủ" uploadButton = "Tải lên máy chủ" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "Quay lại" pdfTools = "Công cụ PDF" placeholder = "Chọn một công cụ để bắt đầu" premiumFeature = "Tính năng Premium:" -searchTools = "Tìm kiếm công cụ" toolsHeader = "Công cụ" viewAllTools = "Xem tất cả công cụ" @@ -10822,6 +10842,9 @@ removal = "Loại bỏ" signing = "Ký" verification = "Xác minh" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "Không tìm thấy công cụ nào" noTools = "Không có công cụ nào" @@ -10979,6 +11002,13 @@ approver = "Phê duyệt chính sách" editor = "Editor" processor = "Processor" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "Hủy lời mời đến {{email}}? Họ sẽ không thể tham gia bằng liên kết hiện tại." cancelInviteTitle = "Hủy lời mời" @@ -10998,10 +11028,8 @@ title = "Chưa có thành viên" addToTeam = "Thêm vào đội" guestCount = "{{count}} khách" guests = "Khách" -guestsDesc = "Cộng tác viên bên ngoài, giới hạn trong những gì bạn chia sẻ. Chỉ Editor." ledBy = "do {{owner}} dẫn dắt" org = "Tổ chức" -orgDesc = "Chủ sở hữu có quyền hạn toàn tổ chức và phê duyệt chính sách" owners = "{{count}} chủ sở hữu" team = "Đội {{name}}" teamMeta = "{{count}} người" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "Được mời bởi {{who}}" cancel = "Hủy" -count = "{{count}} đang chờ" -desc = "Những người đã được mời nhưng chưa tham gia. Họ giữ một chỗ cho đến khi chấp nhận." expiresInDays_one = "Hết hạn sau {{count}} ngày" expiresInDays_other = "Hết hạn sau {{count}} ngày" expiresToday = "Hết hạn hôm nay" title = "Lời mời đang chờ" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "Đã xảy ra lỗi khi kết nối tới backend, hoặc bạn không có quyền truy cập. Hãy thử lại." title = "Không thể tải thành viên" @@ -11237,12 +11267,15 @@ searchPlaceholder = "Tìm kiếm tệp đính kèm" title = "Tệp đính kèm" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "Tiêu đề dấu trang" bookmarkTitleRequired = "Bắt buộc nhập tiêu đề dấu trang" closeSidebar = "Đóng thanh bên dấu trang" collapseAll = "Thu gọn tất cả dấu trang" +empty = "No bookmarks in this document" expandAll = "Mở rộng tất cả dấu trang" searchPlaceholder = "Tìm kiếm dấu trang" +title = "Bookmarks" [viewer.comments] addComment = "Thêm bình luận" @@ -11264,10 +11297,12 @@ locateAnnotation = "Định vị trong tài liệu" moreActions = "Thao tác khác" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "Trang {{page}}" placingHint = "Nhấp vào một trang để đặt… (hủy)" removeCommentOnly = "Chỉ gỡ bình luận" saveReply = "Lưu trả lời" +searchPlaceholder = "Search comments" title = "Bình luận" typeComment = "Bình luận" typeInsertText = "Chèn văn bản" @@ -11290,6 +11325,7 @@ unsavedDesc = "Bạn có các thay đổi chưa được lưu" closeSidebar = "Đóng thanh bên lớp" hideAll = "Ẩn tất cả lớp" showAll = "Hiển thị tất cả lớp" +title = "Layers" [viewer.link] delete = "Xóa liên kết" @@ -11646,6 +11682,7 @@ exitRedaction = "Thoát chế độ bôi đen" exportAll = "Xuất PDF" exportSelected = "Xuất các trang đã chọn" formFill = "Điền biểu mẫu" +hideToolbar = "Hide toolbar" multiTool = "Công cụ đa năng" panMode = "Chế độ di chuyển" print = "In PDF" @@ -11666,6 +11703,7 @@ selectAll = "Chọn tất cả" selectByNumber = "Chọn theo số trang" selectLanguage = "Chọn ngôn ngữ" share = "Chia sẻ" +showToolbar = "Show toolbar" toggleAnnotations = "Bật/Tắt hiển thị chú thích" toggleAttachments = "Bật/Tắt tệp đính kèm" toggleBookmarks = "Bật/Tắt dấu trang" diff --git a/frontend/editor/public/locales/zh-BO/translation.toml b/frontend/editor/public/locales/zh-BO/translation.toml index 97a01be70d..e16a524f88 100644 --- a/frontend/editor/public/locales/zh-BO/translation.toml +++ b/frontend/editor/public/locales/zh-BO/translation.toml @@ -1,7 +1,6 @@ alphabet = "གསལ་བྱེད།" apply = "ཉེར་སྤྱོད།" applyAndContinue = "保存并离开" -areYouSure = "确定要离开吗?" back = "返回" black = "ནག་པོ" blue = "སྔོན་པོ" @@ -106,6 +105,7 @@ undoSuccess = "已成功撤销操作" unknown = "མི་ཤེས་པ།" unpin = "取消固定文件(工具运行后替换)" unsavedChanges = "您的 PDF 有未保存的更改。" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "未保存的更改" unsupported = "不支持" username = "སྤྱོད་མཁན་མིང་།" @@ -1879,6 +1879,9 @@ width = "宽度" [app] description = "免费的 Adobe Acrobat 替代方案(下载量 1000 万+)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "转换为 PDF/A-3b" convertToPdfA3bDescription = "创建带嵌入附件的归档 PDF" @@ -2933,12 +2936,16 @@ text = "要使这些权限不可更改,请使用添加密码工具设置所有 [chat.actions] copy = "复制消息" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling 代理选项" +assistant = "AI Assistant" clearChat = "清空聊天" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "此操作将使用您的云额度" tooltip = "从屏幕取色" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "选择颜色" [common] +actions = "Actions" back = "返回" cancel = "取消" close = "关闭" +codeSample = "Code sample" collapse = "折叠" confirm = "确认" continue = "继续" copied = "已复制!" copy = "复制" done = "完成" +enterValue = "Enter value" error = "错误" expand = "展开" loading = "正在加载..." next = "下一步" +open = "Open" preview = "预览" previous = "上一步" refresh = "刷新" remaining = "剩余" retry = "重试" save = "保存" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "清除所选" @@ -3185,6 +3199,7 @@ title = "压缩方式" [compress.settings] desiredSize = "期望的文件大小" desiredSizePlaceholder = "输入大小" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "压缩是减少文件大小的简便方法。选择“文件大小”可输入目标大小,我们将为您调整质量;选择“质量”可手动设置压缩强度。" @@ -3387,6 +3402,24 @@ pdfOptions = "PDF 选项" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "请先选择源格式" settings = "设置" single = "单个" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "搜索..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "编辑" @@ -3824,11 +3861,13 @@ shareSelected = "共享所选" sharing = "共享" showAll = "显示全部" showHistory = "显示历史" +sortBy = "Sort files" sortByDate = "按日期排序" sortByName = "按名称排序" sortBySize = "按大小排序" storage = "存储" storageState = "存储" +storageUsed = "Storage used" synced = "已同步" title = "上传 PDF 文件" toolChain = "已应用的工具" @@ -3874,8 +3913,12 @@ addFiles = "添加文件" addingFiles = "正在添加文件…" collapse = "折叠侧边栏" customizeGroups = "自定义分组" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "打开文件以开始" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "展开侧边栏" googleDrive = "Google Drive" googleDriveDisabled = "未配置 Google Drive" @@ -3888,15 +3931,17 @@ openFromComputer = "从电脑打开" openSettings = "打开设置" other = "其他" recent = "最近" -search = "搜索" -searchPlaceholder = "搜索文件..." viewAll = "查看全部 {{count}} 个文件" [fileSidebar.fileItem] closeViewer = "关闭查看器" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "在查看器中打开" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "重置为默认值" subtitle = "将文件归入父类别。向类别添加现有或新标签、重命名类别,或创建自己的类别。不属于任何类别的文件会显示在“其他”下。" title = "侧边栏类别" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "添加到工作区" addToWorkspaceCount = "添加 {{count}} 个到工作区" @@ -3918,7 +3971,6 @@ backToMyFiles = "返回到我的文件" breadcrumbs = "文件夹路径" cancel = "取消" classification = "分类" -clearSearch = "清除搜索" clearSelection = "清除选择" closeDetails = "关闭详情" create = "创建" @@ -3950,6 +4002,7 @@ downloadAll = "全部下载" downloadVersion = "下载此版本" dropOverlay = "拖放文件以上传" dropOverlaySub = "文件会先位于“本地”。使用“移动到”或“保存到云端”将它们整理到文件夹中。" +duplicate = "Duplicate" file = "文件" fileInfo = "File info" fileMenu = "文件操作" @@ -3983,8 +4036,6 @@ resizeFolderTree = "调整文件夹树大小(方向键,按住 Shift 为更 save = "保存" saveToServer = "保存到服务器" saveToServerDisabledHint = "此服务器未启用保存到服务器。请联系管理员启用。" -search = "搜索" -searchPlaceholder = "搜索此文件夹及其子文件夹" selectAll = "全选" selectAllHint = "点击全选。提示:按住 Ctrl(或 Cmd)可逐个添加文件,按住 Shift 可选择一个范围。" selectedCount = "已选 {{count}} 个" @@ -4044,6 +4095,10 @@ title = "还没有云端文件" hint = "未上传而保存的文件会留在这里。拖放文件以添加。" title = "没有仅本地文件" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "你打开或编辑过的文件会显示在这里。" title = "尚无修改" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "无法删除文件夹。" deleteFolderFailedDetail = "无法删除文件夹:{{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "无法更新文件夹外观。" folderAppearanceFailedDetail = "无法更新文件夹外观:{{message}}" moveFilesFailed = "无法移动文件。" @@ -4113,6 +4170,11 @@ localHint = "仅存储在此浏览器中" shared = "已共享" sharedHint = "通过链接与你共享" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "最早在前" modifiedDesc = "最新在前" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "存储中暂无可用文件。请先打开一些文件 open = "打开" openFile = "打开文件" openFiles = "打开多个文件" +selectFile = "Select {{name}}" selectFromStorage = "从存储中选择" upload = "上传" uploadFile = "上传文件" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "全部已保存" +analyzingFields = "Analysing form fields..." extractCsvError = "提取 CSV 失败" extractXlsxError = "提取 XLSX 失败" +filled = "filled" flattenAfterFilling = "填写后扁平化" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "必填" requiredFieldsError = "请填写所有必填字段" rescanFields = "重新扫描字段" @@ -4795,9 +4863,6 @@ title = "པར་རིས་ནས་ PDF ལ།" [imageToPdf] tags = "བསྒྱུར་བ།,པར་རིས།,jpg,པར།,འདྲ་པར།" -[infoBanner] -dismiss = "关闭" - [invite] acceptError = "创建账户失败" accountFor = "正在为其创建账户" @@ -4979,6 +5044,34 @@ uploadSuccess = "上传成功!" uploadSuccessMessage = "您的图像已传输。" validating = "正在验证会话..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "扫码上传照片。图像将自动转换为 PDF。" descriptionNoConvert = "从移动设备扫码上传照片。" @@ -4997,8 +5090,13 @@ title = "从手机上传" tags = "ལག་ཆ་མང་པོ།,བཀོལ་སྤྱོད་མང་པོ།,UI,མཐེབ་གནོན་འཐེན་པ།,མདུན་ངོས།,མཁོ་མཁན་ཕྱོགས།,སྤྱོད་སྒོ།,འགུལ་སྐྱོད།,སུབ་པ།,གནས་སྤོ།,བགོ་བ།" title = "PDF ལག་ཆ་མང་པོ།" -[navbar] -search = "འཚོལ་བཤེར།" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "认证未成功。您可以关闭此窗口并重试。" @@ -5127,6 +5225,7 @@ activeFiles = "活动文件 视图显示您加载到工具中 allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "关闭" cropSettings = "现在我们已选择要裁剪的文件,可以配置裁剪工具以选择我们希望裁剪到的区域。" +dialogLabel = "Onboarding" fileCheckbox = "点击其中一个文件即可将其选中进行处理。您可以选择多个文件执行批量操作。" fileReplacement = "修改后的文件会自动替换工作台中的原文件,方便您继续通过更多工具处理。" filesButton = "文件 按钮位于快速访问栏,可用于上传需要使用工具处理的 PDF。" @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF ནས་ PDF/A ལ།" tags = "ཡིག་མཛོད།,དུས་ཡུན་རིང་པོ།,ཚད་ལྡན།,བསྒྱུར་བ།,ཉར་ཚགས།,སྲུང་སྐྱོབ།" title = "PDF ནས་ PDF/A ལ།" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "打印,标准,转换,生产,印前,归档" title = "PDF 转 PDF/X" @@ -6045,6 +6151,7 @@ small = "500 点" xsmall = "100 点" [plan.availablePlans] +currency = "Billing currency" subtitle = "选择最符合您需求的套餐" title = "可用套餐" @@ -6624,6 +6731,7 @@ reachedTitle = "已达到月度支出限额" title = "无法打开 Stripe 门户" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "已使用 {{allowance}} 个免费 PDFs 中的额度" capSuffix_other = "已使用 {{allowance}} 个免费 PDFs 中的额度" eyebrow = "Processor 试用" @@ -6873,6 +6981,32 @@ label = "Jira" description = "当策略运行时发送文档或通知电子邮件。" label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "将处理后的文档归档到 Nextcloud。" @@ -7077,11 +7211,6 @@ title = "官方 SDKs" beta = "Beta" deprecated = "已弃用" -[portal.docs.search] -empty = "没有匹配的文档" -placeholder = "搜索文档" -results = "{{count}} 个结果" - [portal.docs.skills] eyebrow = "技能" lead = "捆绑的命名能力,您的智能体可将其作为单个工具调用。每项技能都是附带评估的确定性操作链。" @@ -7180,11 +7309,11 @@ editorAction = "编辑器" empty = "没有符合此筛选条件的文档。" rowActions = "行操作" sensitiveLabel = "敏感" -sensitiveTitle = "敏感 — 需要访问权限" [portal.documents.table.columns] action = "管道 / 操作" document = "文档" +labels = "Labels" product = "产品" status = "状态" time = "时间" @@ -7200,6 +7329,7 @@ host = "主机" lastSeen = "上次在线" region = "区域" status = "状态" +target = "Target" version = "版本" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "此视图遇到意外错误。请重试,或从侧边栏选择 retry = "重试" title = "此页面出现问题" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "返回" body = "下载桌面应用或自托管服务器。" @@ -7380,11 +7560,6 @@ cancel = "取消" confirm = "撤销密钥" title = "撤销 API 密钥" -[portal.infrastructure.attestationLabel] -attested = "已认证" -inScope = "在范围内" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "按类别筛选审计事件" heading = "审计日志" @@ -7456,11 +7631,6 @@ info = "信息" success = "成功" warning = "警告" -[portal.infrastructure.certLabel] -certified = "已认证" -inProgress = "进行中" -notStarted = "未开始" - [portal.infrastructure.createKey] cancel = "取消" createKey = "创建密钥" @@ -7474,238 +7644,10 @@ subtitleCreated = "立即复制此密钥 — 它不会再次显示。" title = "创建 API 密钥" titleCreated = "密钥已创建" -[portal.infrastructure.deployLabel] -live = "在线" -queued = "已排队" -rolledBack = "已回滚" -rolling = "正在发布" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/分钟" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "部署者" -environment = "环境" -product = "产品" -status = "状态" -version = "版本" -when = "时间" - -[portal.infrastructure.deployments.recent] -heading = "最近部署" -subheading = "跨产品和环境的最新发布。" - -[portal.infrastructure.deployments.regionColumns] -instances = "实例" -latency = "延迟" -load = "负载" -p99 = "P99" -region = "区域" -status = "状态" -throughput = "吞吐量" -uptime = "正常运行时间" -version = "版本" - -[portal.infrastructure.deployments.regions] -heading = "区域" -subheading = "每个已部署 Stirling 区域的实时健康状况 — 延迟、负载和发布版本。" - -[portal.infrastructure.deployments.regions.empty] -description = "工作区完成配置后,已部署区域会显示在这里。" -title = "没有已部署区域" - [portal.infrastructure.keyLabel] active = "活跃" revoked = "已撤销" -[portal.infrastructure.modelLabel] -active = "活跃" -degraded = "性能下降" -disabled = "已禁用" - -[portal.infrastructure.models] -heading = "模型" -msValue = "{{value}} ms" -subheading = "为您工作区中的文档处理提供支持的模型目录和路由。" - -[portal.infrastructure.models.byom] -description = "注册本地或自托管模型,并将其固定到某个区域以进行受数据驻留约束的处理。" -title = "自带模型" - -[portal.infrastructure.models.catalogue] -heading = "目录" -sub = "您的工作区可用的托管模型,包含实时延迟和成本。" -subEnterprise = "托管、自带和本地模型 — 支持按区域固定。" - -[portal.infrastructure.models.catalogue.empty] -description = "您工作区目录中的模型会显示在这里。" -title = "没有可用模型" - -[portal.infrastructure.models.columns] -cost = "成本" -latency = "延迟" -load = "负载" -model = "模型" -status = "状态" -type = "类型" -version = "版本" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/次调用" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "活跃模型" -avgLatency = "平均延迟" -included = "已包含" -monthlySpend = "每月模型支出" - -[portal.infrastructure.models.routing] -empty = "未配置路由规则。" -heading = "路由规则" -sub = "由哪个模型处理每个操作。没有匹配到更具体规则时,将应用默认规则。" -subLocked = "将操作路由到特定模型 — 付费计划可用。" - -[portal.infrastructure.models.routing.lockedBanner] -description = "升级到 Pro 以控制哪个模型处理每个操作和文档类型。" -title = "模型路由是付费功能" - -[portal.infrastructure.models.routingColumns] -default = "默认" -docType = "文档类型" -modelForAria = "{{operation}} 的模型" -operation = "操作" -routedTo = "路由至" - -[portal.infrastructure.modelTypeLabel] -classification = "分类" -extraction = "提取" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "性能下降" -down = "宕机" -healthy = "健康" - -[portal.infrastructure.security.access.byok] -description = "从您自己的 KMS 提供密钥。Stirling 使用它加密,但仍可读取。" -label = "自带密钥 (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "密钥永不离开您的 KMS。Stirling 仅持有密文。" -label = "自持密钥 (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling 管理加密密钥。最简单 — 您无需进行密钥运维。" -label = "Stirling 持有的密钥" - -[portal.infrastructure.security.accessPolicy] -heading = "文档访问策略" -subheading = "控制谁可以解密静态存储的已处理文档。" - -[portal.infrastructure.security.attestations] -heading = "合规证明" -noReport = "无可用报告" -subheading = "按框架展示的审计态势,并可查看已证明控制项的报告。" -viewReport = "查看报告 →" - -[portal.infrastructure.security.compliance] -heading = "合规" -subheading = "覆盖 Stirling 平台的证明和认证。" - -[portal.infrastructure.security.empty] -description = "您工作区的安全配置会显示在这里。" -title = "安全态势不可用" - -[portal.infrastructure.security.hyokBanner] -description = "使用 HYOK 时,加密密钥永不离开您的 KMS。Stirling 仅存储和处理密文,您可随时吊销。" -title = "Stirling 无法解密您的文档" - -[portal.infrastructure.security.ipAllowlist] -empty = "未配置 IP 范围 — 允许所有 IP。" -heading = "IP 允许列表" -sub = "API 访问限制为这些 CIDR 范围。" -subLocked = "将 API 访问限制为已知 IP 范围 — 付费计划可用。" - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "升级到 Pro 以将 API 访问限制到特定网络。" -title = "IP 允许列表是付费功能" - -[portal.infrastructure.security.ipColumns] -added = "已添加" -addedBy = "添加者" -cidr = "CIDR" -label = "标签" - -[portal.infrastructure.security.keyManagement] -algorithm = "算法" -heading = "加密密钥管理" -keyId = "密钥标识符" -lastRotated = "上次轮换" -rotateKey = "轮换密钥" -rotationPolicy = "轮换策略" -subheading = "用于加密静态文档的密钥托管 — 谁可以解密,以及密钥如何轮换。" - -[portal.infrastructure.security.managedBanner] -description = "自带密钥 (BYOK) 和自持密钥 (HYOK) 托管在企业版中可用。升级后可从您自己的 KMS 提供密钥。" -title = "您当前计划中的密钥由 Stirling 管理" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "亚太地区" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR 数据边界" -label = "欧盟" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "美国" - -[portal.infrastructure.security.residencyHeader] -heading = "数据驻留" -subheading = "文档存储和处理的位置。" - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "已使用 {{value}}" - -[portal.infrastructure.storage.empty] -description = "已连接的存储和用量会显示在这里。" -title = "未配置存储" - -[portal.infrastructure.storage.lifecycle] -active = "活跃" -activeRange = "0–{{value}}d" -archived = "已归档" -coldStorage = "冷存储" -deleted = "已删除" -never = "从不" -purged = "已清除" - -[portal.infrastructure.storage.providers] -connect = "连接" -connected = "已连接" -heading = "已连接的提供商" -subheading = "已处理工件写入的位置。" - -[portal.infrastructure.storage.retention] -heading = "保留" -subheading = "工件在生命周期删除前保留多长时间。" -windowLabel = "默认保留窗口" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} 天" -days_other = "{{count}} 天" -never = "永不删除" - -[portal.infrastructure.storage.totalUsage] -heading = "总用量" -progressLabel = "已用存储" -subheading = "所有已连接提供商消耗的存储。" - [portal.infrastructure.tabs] apiKeys = "API 密钥" audit = "审计日志" @@ -7715,11 +7657,9 @@ security = "安全" storage = "存储" [portal.integrations] -addAnother = "再添加一个" availableHeading = "可用" comingSoonHeading = "即将推出" connect = "连接" -connectedHeading = "已连接" connectionCount_one = "{{count}} 个连接" connectionCount_other = "{{count}} 个连接" customApi = "自定义 API" @@ -7737,6 +7677,10 @@ security = "安全" signing = "签名" storage = "存储" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "已连接" @@ -7767,7 +7711,6 @@ integrations = "集成" pipelines = "管道" policies = "策略" procurement = "采购" -settings = "设置" sources = "来源" usage = "用量与账单" users = "用户" @@ -7784,7 +7727,7 @@ title = "管道" newPipeline = "新建管道" [portal.pipelines.builder] -addStep = "添加工具" +activate = "Activate" back = "返回管道" cannotFollow = "无法接收 {{produced}}" chooseAccount = "选择一个账户" @@ -7792,28 +7735,38 @@ chooseDestination = "选择一个目标" chooseOperation = "选择此步骤执行的操作" chooseSource = "选择一个来源" discard = "放弃更改" -enabled = "已启用" inputs = "输入" inputSource = "输入来源" inputTrigger = "触发器" keepEditing = "继续编辑" +moreActions = "More actions" needsConfiguring = "需要设置" -needsUpload = "需要上传的文件" -noSources = "尚未连接来源。创建一个以将其用作输入。" +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "没有工具匹配您的搜索。" -pipelineSettings = "管道设置" +pause = "Pause" +rename = "Rename pipeline" searchTools = "搜索工具" -selectToolBody = "添加工具以构建您的管道。" -selectToolTitle = "尚无工具" sendToSystem = "发送到另一个系统" stepsIncompatible = "这些步骤无法在其上一步生成的内容上运行:{{tools}}。" stepsNeedSetup = "保存前这些步骤仍需要设置:{{tools}}。" -toolSettings = "工具设置" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "无法识别的操作,已按原样保留。" unsavedBody = "您有未保存的更改。离开前保存,还是放弃?" unsavedTitle = "未保存的更改" -uploadUnsupported = "管道尚不支持上传的文件,因此这些步骤无法保存:{{tools}}。" usesDefaults = "使用默认设置运行" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "合并上一步中的每个文件" @@ -7825,31 +7778,36 @@ undeclared-operation = "无法检查此步骤接受的内容" [portal.pipelines.composer] addTool = "添加工具" -cancel = "取消" -chainEmpty = "添加工具以开始构建您的管道。" create = "创建管道" +createPaused = "Create paused" editingUnsupported = "尚不支持显示这些工具参数用于编辑。" -moveDown = "下移" -moveUp = "上移" +editSource = "Edit source" name = "名称" namePlaceholder = "例如:修订扫描" noToolSettings = "此工具没有可配置的设置。" -operations_one = "操作({{count}})" -operations_other = "操作({{count}})" output = "输出" -removeStep = "移除操作" save = "保存更改" scheduleEvery = "每隔" -sources = "来源" -sourcesLoading = "正在加载来源..." trigger = "触发器" triggerManual = "仅手动" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "天" hours = "小时" minutes = "分钟" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "删除 \"{{name}}\"?此操作无法撤销。" cancel = "取消" @@ -7867,6 +7825,37 @@ connectSource = "连接来源" description = "创建您的第一个管道:选择运行的来源,串联操作,并选择输出位置。" title = "尚无管道" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "活跃" paused = "已暂停" @@ -7879,7 +7868,6 @@ completed_one = "运行已完成。" completed_other = "全部 {{count}} 次运行已完成。" empty = "没有可运行内容:来源中没有要处理的文档。" failed = "运行失败:{{error}}" -historyCleared = "历史记录已清除。下一次运行将重新处理当前来源中的所有内容。" inFlight = "没有新的可运行内容:文档仍在从早先的运行中处理中。" parked_one = "无需运行:{{count}} 个文档此前失败并已暂存。修复原因后,清除历史记录以重试。" parked_other = "无需运行:{{count}} 个文档此前失败并已暂存。修复原因后,清除历史记录以重试。" @@ -7895,6 +7883,7 @@ name = "管道" sources = "来源" status = "状态" steps = "步骤" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "文件夹监视" @@ -8209,6 +8198,24 @@ label = "附加到 Jira 问题" description = "将处理后的文档作为附件发送。" label = "通过电子邮件发送文档(Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "将处理后的文档写入文件夹。" label = "上传到 Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "已达上限" subtitle = "您的免费试用持续到 {{date}}。无需银行卡。" title = "企业试用" -[portal.search] -ariaLabel = "搜索" -placeholder = "搜索 Stirling — 端点、管道、文档…" - -[portal.search.empty] -noActionsDescription = "快速操作可用后将显示在这里。" -noActionsTitle = "无快速操作" -noMatches = "没有匹配 \"{{query}}\" 的结果" -noMatchesDescription = "尝试其他关键词或浏览目录。" - [portal.settings.groups] admin = "管理员" @@ -8743,10 +8740,6 @@ cancel = "取消" confirm = "删除" title = "删除来源?" -[portal.sources.empty] -description = "连接一个文件夹(很快还可连接云存储),让您的政策有位置可拉取文档。" -title = "尚未连接来源" - [portal.sources.kpi] inUse = "使用中" total = "连接" @@ -8785,6 +8778,7 @@ unused = "未使用" documents = "文档" source = "来源" status = "状态" +type = "Type" usedBy = "政策" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "政策与隐私" [settings.preferences] title = "首选项" -[settings.search] -placeholder = "搜索设置页面..." - [settings.security] description = "更新您的密码以确保账户安全。" title = "安全" @@ -10128,6 +10119,7 @@ last = "ཤོག་ངོས་མཐའ་མ།" maintainRatio = "བསྡུར་ཚད་རྒྱུན་འཁྱོངས་སྒོ་རྒྱག་པ།" next = "ཤོག་ངོས་རྗེས་མ།" noSavedSigs = "ཉར་ཚགས་བྱས་པའི་མིང་རྟགས་མ་རྙེད།" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "སྒེར་གྱི་མིང་རྟགས།" previous = "ཤོག་ངོས་སྔོན་མ།" redo = "重做" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "背景移除失败" hint = "上传您的签名 PNG 或 JPG 图片" label = "上传签名图片" placeholder = "选择图像文件" +previewAlt = "Current image signature" processing = "正在处理图像..." removeBackground = "移除白色背景(变为透明)" @@ -10179,6 +10172,17 @@ saved = "在上方选择一个已保存签名,然后在 PDF 上任意点击进 text = "在上方输入您的姓名后,在 PDF 任意位置点击以放置签名。" title = "如何添加签名" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "移动签名" pause = "暂停放置" @@ -10625,6 +10629,23 @@ title = "上传到服务器" updateButton = "在服务器上更新" uploadButton = "上传到服务器" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "返回" pdfTools = "PDF 工具" placeholder = "选择一个工具以开始" premiumFeature = "高级功能:" -searchTools = "搜索工具" toolsHeader = "工具" viewAllTools = "查看所有工具" @@ -10822,6 +10842,9 @@ removal = "移除" signing = "签名" verification = "验证" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "未找到工具" noTools = "没有可用的工具" @@ -10979,6 +11002,13 @@ approver = "批准政策" editor = "编辑器" processor = "处理器" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "要取消对 {{email}} 的邀请吗?他们将无法使用当前链接加入。" cancelInviteTitle = "取消邀请" @@ -10998,10 +11028,8 @@ title = "尚无成员" addToTeam = "添加到团队" guestCount = "{{count}} 位访客" guests = "访客" -guestsDesc = "外部协作者,范围限于您共享的内容。仅编辑器。" ledBy = "由 {{owner}} 领导" org = "组织" -orgDesc = "拥有组织范围权限和政策批准权的所有者" owners = "{{count}} 位所有者" team = "{{name}} 团队" teamMeta = "{{count}} 人" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "由 {{who}} 邀请" cancel = "取消" -count = "{{count}} 个待处理" -desc = "已邀请但尚未加入的人员。他们在接受前会占用一个席位。" expiresInDays_one = "{{count}} 天后过期" expiresInDays_other = "{{count}} 天后过期" expiresToday = "今天过期" title = "待处理邀请" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "连接后端时出错,或者你没有访问权限。请重试。" title = "无法加载成员" @@ -11237,12 +11267,15 @@ searchPlaceholder = "搜索附件" title = "附件" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "书签标题" bookmarkTitleRequired = "必须填写书签标题" closeSidebar = "关闭书签侧边栏" collapseAll = "折叠所有书签" +empty = "No bookmarks in this document" expandAll = "展开所有书签" searchPlaceholder = "搜索书签" +title = "Bookmarks" [viewer.comments] addComment = "添加评论" @@ -11264,10 +11297,12 @@ locateAnnotation = "在文档中定位" moreActions = "更多操作" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "第 {{page}} 页" placingHint = "单击页面进行放置…(取消)" removeCommentOnly = "仅移除评论" saveReply = "保存回复" +searchPlaceholder = "Search comments" title = "评论" typeComment = "评论" typeInsertText = "插入文本" @@ -11290,6 +11325,7 @@ unsavedDesc = "您有未保存的更改" closeSidebar = "关闭图层侧边栏" hideAll = "隐藏所有图层" showAll = "显示所有图层" +title = "Layers" [viewer.link] delete = "删除链接" @@ -11646,6 +11682,7 @@ exitRedaction = "退出涂黑模式" exportAll = "导出 PDF" exportSelected = "导出所选页面" formFill = "填写表单" +hideToolbar = "Hide toolbar" multiTool = "多功能工具" panMode = "平移模式" print = "打印 PDF" @@ -11666,6 +11703,7 @@ selectAll = "全选" selectByNumber = "按页码选择" selectLanguage = "选择语言" share = "共享" +showToolbar = "Show toolbar" toggleAnnotations = "切换注释可见性" toggleAttachments = "切换附件" toggleBookmarks = "切换书签" diff --git a/frontend/editor/public/locales/zh-CN/translation.toml b/frontend/editor/public/locales/zh-CN/translation.toml index e45a5bfdc1..c63f0e2b79 100644 --- a/frontend/editor/public/locales/zh-CN/translation.toml +++ b/frontend/editor/public/locales/zh-CN/translation.toml @@ -1,7 +1,6 @@ alphabet = "字母表" apply = "应用" applyAndContinue = "应用并继续" -areYouSure = "确定要离开吗?" back = "返回" black = "黑色" blue = "蓝色" @@ -106,6 +105,7 @@ undoSuccess = "已成功撤销操作" unknown = "未知" unpin = "取消固定文件(在工具运行后替换)" unsavedChanges = "您的 PDF 有未保存的更改。您想做什么?" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "未保存的更改" unsupported = "不受支持" username = "用户名" @@ -1879,6 +1879,9 @@ width = "宽度" [app] description = "免费的 Adobe Acrobat 替代品(下载量 1000 万+)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "转换为 PDF/A-3b" convertToPdfA3bDescription = "创建带有嵌入附件的归档 PDF" @@ -2933,12 +2936,16 @@ text = "要使这些权限不可更改,请使用“添加密码”工具设置 [chat.actions] copy = "复制消息" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling 代理选项" +assistant = "AI Assistant" clearChat = "清空聊天" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "此操作将使用您的云额度" tooltip = "从屏幕拾取颜色" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "选择颜色" [common] +actions = "Actions" back = "返回" cancel = "取消" close = "关闭" +codeSample = "Code sample" collapse = "折叠" confirm = "确认" continue = "继续" copied = "已复制!" copy = "复制" done = "完成" +enterValue = "Enter value" error = "错误" expand = "展开" loading = "正在加载..." next = "下一页" +open = "Open" preview = "预览" previous = "上一页" refresh = "刷新" remaining = "剩余" retry = "重试" save = "保存" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "清除已选择" @@ -3185,6 +3199,7 @@ title = "压缩方式" [compress.settings] desiredSize = "期望的文件大小" desiredSizePlaceholder = "输入大小" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "压缩是减小文件大小的简便方法。选择“文件大小”可输入目标大小,我们将为您自动调整质量;选择“质量”可手动设置压缩强度。" @@ -3387,6 +3402,24 @@ pdfOptions = "PDF 选项" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "请先选择源格式" settings = "设置" single = "单个" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "搜索…" +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "编辑" @@ -3824,11 +3861,13 @@ shareSelected = "共享所选" sharing = "共享" showAll = "显示全部" showHistory = "显示历史" +sortBy = "Sort files" sortByDate = "按日期排序" sortByName = "按名称排序" sortBySize = "按大小排序" storage = "存储" storageState = "存储" +storageUsed = "Storage used" synced = "已同步" title = "上传 PDF 文件" toolChain = "已应用工具" @@ -3874,8 +3913,12 @@ addFiles = "添加文件" addingFiles = "正在添加文件…" collapse = "折叠侧边栏" customizeGroups = "自定义分组" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "打开文件以开始" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "展开侧边栏" googleDrive = "Google Drive" googleDriveDisabled = "未配置 Google Drive" @@ -3888,15 +3931,17 @@ openFromComputer = "从电脑打开" openSettings = "打开设置" other = "其他" recent = "最近" -search = "搜索" -searchPlaceholder = "搜索文件..." viewAll = "查看全部 {{count}} 个文件" [fileSidebar.fileItem] closeViewer = "关闭查看器" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "在查看器中打开" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "重置为默认值" subtitle = "将文件分组到父类别中。向类别添加现有或新标签、重命名类别,或创建自己的类别。不属于任何类别的文件会显示在“其他”下。" title = "侧边栏类别" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "添加到工作区" addToWorkspaceCount = "将 {{count}} 个添加到工作区" @@ -3918,7 +3971,6 @@ backToMyFiles = "返回“我的文件”" breadcrumbs = "文件夹路径" cancel = "取消" classification = "分类" -clearSearch = "清除搜索" clearSelection = "清除选择" closeDetails = "关闭详情" create = "创建" @@ -3950,6 +4002,7 @@ downloadAll = "全部下载" downloadVersion = "下载此版本" dropOverlay = "拖放文件以上传" dropOverlaySub = "文件起始于“本地”。使用 '移动到' 或 '保存到云端' 将其整理到文件夹中。" +duplicate = "Duplicate" file = "文件" fileInfo = "File info" fileMenu = "文件操作" @@ -3983,8 +4036,6 @@ resizeFolderTree = "调整文件夹树大小(方向键,按住 Shift 步长 save = "保存" saveToServer = "保存到服务器" saveToServerDisabledHint = "此服务器未启用保存到服务器功能。请联系您的管理员启用。" -search = "搜索" -searchPlaceholder = "搜索此文件夹及其子文件夹" selectAll = "全选" selectAllHint = "点击可全选。提示:按住 Ctrl(或 Cmd)可逐个添加文件,按住 Shift 可选择范围。" selectedCount = "已选择 {{count}} 个" @@ -4044,6 +4095,10 @@ title = "尚无云端文件" hint = "未上传而保存的文件会保留在此处。拖放文件以添加。" title = "没有仅本地文件" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "您打开或编辑的文件会出现在这里。" title = "尚未有修改" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "无法删除文件夹。" deleteFolderFailedDetail = "无法删除文件夹:{{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "无法更新文件夹外观。" folderAppearanceFailedDetail = "无法更新文件夹外观:{{message}}" moveFilesFailed = "无法移动文件。" @@ -4113,6 +4170,11 @@ localHint = "仅存储在此浏览器中" shared = "共享" sharedHint = "通过链接与您共享" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "最早优先" modifiedDesc = "最新优先" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "存储中无可用文件。请先打开一些文件。" open = "打开" openFile = "打开文件" openFiles = "打开文件" +selectFile = "Select {{name}}" selectFromStorage = "从存储中选择" upload = "上传" uploadFile = "上传文件" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "全部已保存" +analyzingFields = "Analysing form fields..." extractCsvError = "提取 CSV 失败" extractXlsxError = "提取 XLSX 失败" +filled = "filled" flattenAfterFilling = "填写后扁平化" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "必填" requiredFieldsError = "请填写所有必填字段" rescanFields = "重新扫描字段" @@ -4795,9 +4863,6 @@ title = "图片转 PDF" [imageToPdf] tags = "转换、图像、JPG、图片、照片" -[infoBanner] -dismiss = "关闭" - [invite] acceptError = "创建账户失败" accountFor = "正在为以下用户创建账户" @@ -4979,6 +5044,34 @@ uploadSuccess = "上传成功!" uploadSuccessMessage = "您的图像已传输。" validating = "正在验证会话..." +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "扫码上传照片。图像会自动转换为 PDF。" descriptionNoConvert = "使用手机扫码上传照片。" @@ -4997,8 +5090,13 @@ title = "从手机上传" tags = "多工具,多操作,用户界面,点击拖动,前端,客户端" title = "PDF 多功能工具" -[navbar] -search = "搜索" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "认证未成功。您可以关闭此窗口后重试。" @@ -5127,6 +5225,7 @@ activeFiles = "活动文件 视图显示您已加载到工具 allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "关闭" cropSettings = "现在我们已选择要裁剪的文件,可以配置裁剪工具,选择要将 PDF 裁剪到的区域。" +dialogLabel = "Onboarding" fileCheckbox = "单击某个文件即可选择其进行处理。您可以选择多个文件以进行批量操作。" fileReplacement = "修改后的文件会自动替换工作台中的原始文件,方便您继续使用更多工具处理。" filesButton = "快速访问栏上的 文件 按钮可用于上传 PDF,以便使用工具。" @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "将 PDF 转换为 PDF/A" tags = "归档、长期、标准、转换、存储、保存" title = "PDF 转 PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "打印,标准,转换,生产,印前,归档" title = "PDF 转为 PDF/X" @@ -6045,6 +6151,7 @@ small = "500 积分" xsmall = "100 积分" [plan.availablePlans] +currency = "Billing currency" subtitle = "选择符合您需求的套餐" title = "可用套餐" @@ -6624,6 +6731,7 @@ reachedTitle = "已达到每月支出限额" title = "无法打开 Stripe 门户" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "已使用 {{allowance}} 个免费 PDFs 中的额度" capSuffix_other = "已使用 {{allowance}} 个免费 PDFs 中的额度" eyebrow = "Processor 试用" @@ -6873,6 +6981,32 @@ label = "Jira" description = "当策略运行时发送文档或通知电子邮件。" label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "将处理后的文档归档到 Nextcloud。" @@ -7077,11 +7211,6 @@ title = "官方 SDKs" beta = "Beta" deprecated = "已弃用" -[portal.docs.search] -empty = "没有匹配的文档" -placeholder = "搜索文档" -results = "{{count}} 个结果" - [portal.docs.skills] eyebrow = "技能" lead = "打包的命名能力,您的智能体可将其作为单个工具调用。每项技能都是附带评估的确定性操作链。" @@ -7180,11 +7309,11 @@ editorAction = "编辑器" empty = "没有文档符合此筛选条件。" rowActions = "行操作" sensitiveLabel = "敏感" -sensitiveTitle = "敏感 — 需要访问权限" [portal.documents.table.columns] action = "流水线 / 操作" document = "文档" +labels = "Labels" product = "产品" status = "状态" time = "时间" @@ -7200,6 +7329,7 @@ host = "主机" lastSeen = "最后在线" region = "区域" status = "状态" +target = "Target" version = "版本" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "此视图遇到意外错误。请重试,或从侧边栏选择 retry = "重试" title = "此页面出现问题" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "返回" body = "下载桌面应用或自托管服务器。" @@ -7380,11 +7560,6 @@ cancel = "取消" confirm = "撤销密钥" title = "撤销 API 密钥" -[portal.infrastructure.attestationLabel] -attested = "已证明" -inScope = "范围内" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "按类别筛选审计事件" heading = "审计日志" @@ -7456,11 +7631,6 @@ info = "信息" success = "成功" warning = "警告" -[portal.infrastructure.certLabel] -certified = "已认证" -inProgress = "进行中" -notStarted = "未开始" - [portal.infrastructure.createKey] cancel = "取消" createKey = "创建密钥" @@ -7474,238 +7644,10 @@ subtitleCreated = "立即复制此密钥 — 它不会再次显示。" title = "创建 API 密钥" titleCreated = "密钥已创建" -[portal.infrastructure.deployLabel] -live = "上线" -queued = "已排队" -rolledBack = "已回滚" -rolling = "正在发布" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/分钟" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "部署者" -environment = "环境" -product = "产品" -status = "状态" -version = "版本" -when = "时间" - -[portal.infrastructure.deployments.recent] -heading = "最近部署" -subheading = "各产品和环境中的最新发布。" - -[portal.infrastructure.deployments.regionColumns] -instances = "实例" -latency = "延迟" -load = "负载" -p99 = "P99" -region = "区域" -status = "状态" -throughput = "吞吐量" -uptime = "正常运行时间" -version = "版本" - -[portal.infrastructure.deployments.regions] -heading = "区域" -subheading = "每个已部署 Stirling 区域的实时健康状况 — 延迟、负载和发布版本。" - -[portal.infrastructure.deployments.regions.empty] -description = "工作区完成预配后,已部署区域会显示在此处。" -title = "未部署区域" - [portal.infrastructure.keyLabel] active = "活跃" revoked = "已撤销" -[portal.infrastructure.modelLabel] -active = "活跃" -degraded = "已降级" -disabled = "已禁用" - -[portal.infrastructure.models] -heading = "模型" -msValue = "{{value}} ms" -subheading = "为工作区中的文档处理提供支持的模型目录和路由。" - -[portal.infrastructure.models.byom] -description = "注册本地或自托管模型,并将其固定到某个区域,以进行受数据驻留约束的处理。" -title = "自带模型" - -[portal.infrastructure.models.catalogue] -heading = "目录" -sub = "可供工作区使用的托管模型,包含实时延迟和成本。" -subEnterprise = "托管、自带和本地模型 — 支持按区域固定。" - -[portal.infrastructure.models.catalogue.empty] -description = "工作区目录中的模型会显示在此处。" -title = "没有可用模型" - -[portal.infrastructure.models.columns] -cost = "成本" -latency = "延迟" -load = "负载" -model = "模型" -status = "状态" -type = "类型" -version = "版本" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/次调用" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "活跃模型" -avgLatency = "平均延迟" -included = "已包含" -monthlySpend = "每月模型支出" - -[portal.infrastructure.models.routing] -empty = "未配置路由规则。" -heading = "路由规则" -sub = "由哪个模型处理每个操作。当没有更精确的规则匹配时,将应用默认规则。" -subLocked = "将操作路由到特定模型 — 付费套餐可用。" - -[portal.infrastructure.models.routing.lockedBanner] -description = "升级到 Pro,以控制哪个模型处理每种操作和文档类型。" -title = "模型路由是付费功能" - -[portal.infrastructure.models.routingColumns] -default = "默认" -docType = "文档类型" -modelForAria = "{{operation}} 的模型" -operation = "操作" -routedTo = "路由到" - -[portal.infrastructure.modelTypeLabel] -classification = "分类" -extraction = "提取" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "已降级" -down = "停机" -healthy = "健康" - -[portal.infrastructure.security.access.byok] -description = "从您自己的 KMS 提供密钥。Stirling 使用它进行加密,但仍可读取。" -label = "自带密钥 (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "密钥永不离开您的 KMS。Stirling 只持有密文。" -label = "自持密钥 (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling 管理加密密钥。最简单 — 您这边无需密钥运维。" -label = "Stirling 持有的密钥" - -[portal.infrastructure.security.accessPolicy] -heading = "文档访问策略" -subheading = "控制谁可以解密静态存储的已处理文档。" - -[portal.infrastructure.security.attestations] -heading = "合规证明" -noReport = "无可用报告" -subheading = "按框架展示的审计态势,并可查看已证明控制项的报告。" -viewReport = "查看报告 →" - -[portal.infrastructure.security.compliance] -heading = "合规" -subheading = "覆盖 Stirling 平台的证明和认证。" - -[portal.infrastructure.security.empty] -description = "工作区的安全配置会显示在此处。" -title = "安全态势不可用" - -[portal.infrastructure.security.hyokBanner] -description = "使用 HYOK 时,加密密钥永不离开您的 KMS。Stirling 仅存储和处理密文,您可以随时撤销。" -title = "Stirling 无法解密您的文档" - -[portal.infrastructure.security.ipAllowlist] -empty = "未配置 IP 范围 — 允许所有 IP。" -heading = "IP allowlist" -sub = "API 访问仅限这些 CIDR 范围。" -subLocked = "将 API 访问限制为已知 IP 范围 — 付费套餐可用。" - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "升级到 Pro,以将 API 访问限制到特定网络。" -title = "IP allowlist 是付费功能" - -[portal.infrastructure.security.ipColumns] -added = "已添加" -addedBy = "添加者" -cidr = "CIDR" -label = "标签" - -[portal.infrastructure.security.keyManagement] -algorithm = "Algorithm" -heading = "加密密钥管理" -keyId = "密钥标识符" -lastRotated = "最后轮换" -rotateKey = "轮换密钥" -rotationPolicy = "轮换策略" -subheading = "用于加密静态文档的密钥托管 — 谁可以解密,以及密钥如何轮换。" - -[portal.infrastructure.security.managedBanner] -description = "自带密钥 (BYOK) 和自持密钥 (HYOK) 托管在企业版中可用。升级后可从您自己的 KMS 提供密钥。" -title = "在您的套餐中,密钥由 Stirling 管理" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "亚太地区" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR 数据边界" -label = "欧盟" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "美国" - -[portal.infrastructure.security.residencyHeader] -heading = "数据驻留" -subheading = "文档存储和处理的位置。" - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "已使用 {{value}}" - -[portal.infrastructure.storage.empty] -description = "已连接的存储和用量会显示在此处。" -title = "未配置存储" - -[portal.infrastructure.storage.lifecycle] -active = "活跃" -activeRange = "0–{{value}}d" -archived = "已归档" -coldStorage = "冷存储" -deleted = "已删除" -never = "永不" -purged = "已清除" - -[portal.infrastructure.storage.providers] -connect = "连接" -connected = "已连接" -heading = "已连接的提供商" -subheading = "已处理工件的写入位置。" - -[portal.infrastructure.storage.retention] -heading = "保留" -subheading = "工件在生命周期删除前保留的时长。" -windowLabel = "默认保留窗口" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} 天" -days_other = "{{count}} 天" -never = "永不删除" - -[portal.infrastructure.storage.totalUsage] -heading = "总用量" -progressLabel = "已使用存储" -subheading = "所有已连接提供商消耗的存储。" - [portal.infrastructure.tabs] apiKeys = "API 密钥" audit = "审计日志" @@ -7715,11 +7657,9 @@ security = "安全" storage = "存储" [portal.integrations] -addAnother = "再添加一个" availableHeading = "可用" comingSoonHeading = "即将推出" connect = "连接" -connectedHeading = "已连接" connectionCount_one = "{{count}} 个连接" connectionCount_other = "{{count}} 个连接" customApi = "自定义 API" @@ -7737,6 +7677,10 @@ security = "安全" signing = "签名" storage = "存储" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "已连接" @@ -7767,7 +7711,6 @@ integrations = "集成" pipelines = "流水线" policies = "策略" procurement = "采购" -settings = "设置" sources = "来源" usage = "用量与计费" users = "用户" @@ -7784,7 +7727,7 @@ title = "流水线" newPipeline = "新建流水线" [portal.pipelines.builder] -addStep = "添加工具" +activate = "Activate" back = "返回流水线" cannotFollow = "无法接收 {{produced}}" chooseAccount = "选择账户" @@ -7792,28 +7735,38 @@ chooseDestination = "选择目标位置" chooseOperation = "选择此步骤执行的操作" chooseSource = "选择源" discard = "放弃更改" -enabled = "已启用" inputs = "输入" inputSource = "输入源" inputTrigger = "触发器" keepEditing = "继续编辑" +moreActions = "More actions" needsConfiguring = "需要设置" -needsUpload = "需要上传文件" -noSources = "尚未连接源。创建一个源以将其用作输入。" +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "没有工具符合您的搜索。" -pipelineSettings = "流水线设置" +pause = "Pause" +rename = "Rename pipeline" searchTools = "搜索工具" -selectToolBody = "添加工具以构建您的流水线。" -selectToolTitle = "还没有工具" sendToSystem = "发送到另一个系统" stepsIncompatible = "这些步骤无法处理其上一步产生的内容:{{tools}}。" stepsNeedSetup = "保存前仍需设置这些步骤:{{tools}}。" -toolSettings = "工具设置" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "无法识别的操作,已按原样保留。" unsavedBody = "您有未保存的更改。离开前保存,还是放弃更改?" unsavedTitle = "未保存的更改" -uploadUnsupported = "流水线尚不支持上传的文件,因此这些步骤无法保存:{{tools}}。" usesDefaults = "使用默认设置运行" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "合并上一步中的每个文件" @@ -7825,31 +7778,36 @@ undeclared-operation = "无法检查此步骤接受的内容" [portal.pipelines.composer] addTool = "添加工具" -cancel = "取消" -chainEmpty = "添加工具以开始构建您的流水线。" create = "创建流水线" +createPaused = "Create paused" editingUnsupported = "尚不支持显示这些工具参数以进行编辑。" -moveDown = "下移" -moveUp = "上移" +editSource = "Edit source" name = "名称" namePlaceholder = "例如:修订扫描" noToolSettings = "此工具没有可配置的设置。" -operations_one = "操作 ({{count}})" -operations_other = "操作 ({{count}})" output = "输出" -removeStep = "移除操作" save = "保存更改" scheduleEvery = "运行间隔" -sources = "来源" -sourcesLoading = "正在加载来源..." trigger = "触发器" triggerManual = "仅手动" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "天" hours = "小时" minutes = "分钟" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "删除 \"{{name}}\"?此操作无法撤销。" cancel = "取消" @@ -7867,6 +7825,37 @@ connectSource = "连接源" description = "创建您的第一个流水线:选择要运行的来源,串联操作,并选择输出位置。" title = "还没有流水线" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "活跃" paused = "已暂停" @@ -7879,7 +7868,6 @@ completed_one = "运行已完成。" completed_other = "所有 {{count}} 次运行均已完成。" empty = "没有可运行内容:来源中没有要处理的文档。" failed = "运行失败:{{error}}" -historyCleared = "历史记录已清除。下次运行将重新处理源中当前的所有内容。" inFlight = "没有新的内容可运行:文档仍在从先前的运行中处理。" parked_one = "无需运行:{{count}} 个文档之前处理失败并已搁置。修复原因后,清除历史记录以重试。" parked_other = "无需运行:{{count}} 个文档之前处理失败并已搁置。修复原因后,清除历史记录以重试。" @@ -7895,6 +7883,7 @@ name = "流水线" sources = "来源" status = "状态" steps = "步骤" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "文件夹监视" @@ -8209,6 +8198,24 @@ label = "附加到 Jira issue" description = "将处理后的文档作为附件发送。" label = "通过 Email 发送文档(Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "将处理后的文档写入文件夹。" label = "上传到 Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "已达上限" subtitle = "你的免费试用持续到 {{date}}。无需银行卡。" title = "Enterprise 试用" -[portal.search] -ariaLabel = "搜索" -placeholder = "搜索 Stirling — 端点、流水线、文档…" - -[portal.search.empty] -noActionsDescription = "可用后,快捷操作将显示在这里。" -noActionsTitle = "无快捷操作" -noMatches = "没有与“{{query}}”匹配的结果" -noMatchesDescription = "尝试其他关键词或浏览目录。" - [portal.settings.groups] admin = "管理员" @@ -8743,10 +8740,6 @@ cancel = "取消" confirm = "删除" title = "删除源?" -[portal.sources.empty] -description = "连接一个文件夹(很快还会支持云存储),让你的策略有地方拉取文档。" -title = "尚未连接源" - [portal.sources.kpi] inUse = "使用中" total = "连接" @@ -8785,6 +8778,7 @@ unused = "未使用" documents = "文档" source = "源" status = "状态" +type = "Type" usedBy = "策略" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "策略与隐私" [settings.preferences] title = "首选项" -[settings.search] -placeholder = "搜索设置页面..." - [settings.security] description = "更新密码以保障您的账户安全。" title = "安全" @@ -10128,6 +10119,7 @@ last = "末页" maintainRatio = "切换保持长宽比" next = "下一页" noSavedSigs = "未找到已保存的签名" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "个人签名" previous = "上一页" redo = "重做" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "背景移除失败" hint = "上传 PNG 或 JPG 格式的签名图像" label = "上传签名图像" placeholder = "选择图像文件" +previewAlt = "Current image signature" processing = "正在处理图像..." removeBackground = "移除白色背景(变为透明)" @@ -10179,6 +10172,17 @@ saved = "在上方选择一个已保存的签名,然后在 PDF 任意位置点 text = "在上方输入您的姓名后,在 PDF 上任意位置点击以放置签名。" title = "如何添加签名" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "移动签名" pause = "暂停放置" @@ -10625,6 +10629,23 @@ title = "上传到服务器" updateButton = "在服务器上更新" uploadButton = "上传到服务器" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "返回" pdfTools = "PDF 工具" placeholder = "选择一个工具以开始" premiumFeature = "高级功能:" -searchTools = "搜索工具" toolsHeader = "工具" viewAllTools = "查看所有工具" @@ -10822,6 +10842,9 @@ removal = "移除" signing = "签署" verification = "验证" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "未找到工具" noTools = "没有可用的工具" @@ -10979,6 +11002,13 @@ approver = "批准策略" editor = "编辑器" processor = "处理器" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "取消对 {{email}} 的邀请?他们将无法使用当前链接加入。" cancelInviteTitle = "取消邀请" @@ -10998,10 +11028,8 @@ title = "暂无成员" addToTeam = "添加到团队" guestCount = "{{count}} 位访客" guests = "访客" -guestsDesc = "外部协作者,范围限于你共享的内容。仅编辑器。" ledBy = "由 {{owner}} 领导" org = "组织" -orgDesc = "拥有组织范围权限和策略批准权限的所有者" owners = "{{count}} 位所有者" team = "{{name}} 团队" teamMeta = "{{count}} 人" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "由 {{who}} 邀请" cancel = "取消" -count = "{{count}} 个待处理" -desc = "已受邀但尚未加入的人员。在接受邀请之前,他们会占用一个席位。" expiresInDays_one = "{{count}} 天后过期" expiresInDays_other = "{{count}} 天后过期" expiresToday = "今天过期" title = "待处理邀请" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "连接后端时出现问题,或者你没有访问权限。请重试。" title = "无法加载成员" @@ -11237,12 +11267,15 @@ searchPlaceholder = "搜索附件" title = "附件" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "书签标题" bookmarkTitleRequired = "书签标题为必填项" closeSidebar = "关闭书签侧边栏" collapseAll = "折叠所有书签" +empty = "No bookmarks in this document" expandAll = "展开所有书签" searchPlaceholder = "搜索书签" +title = "Bookmarks" [viewer.comments] addComment = "添加评论" @@ -11264,10 +11297,12 @@ locateAnnotation = "在文档中定位" moreActions = "更多操作" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "第 {{page}} 页" placingHint = "单击页面以放置… (取消)" removeCommentOnly = "仅移除评论" saveReply = "保存回复" +searchPlaceholder = "Search comments" title = "评论" typeComment = "评论" typeInsertText = "插入文本" @@ -11290,6 +11325,7 @@ unsavedDesc = "您有未保存的更改" closeSidebar = "关闭图层侧边栏" hideAll = "隐藏所有图层" showAll = "显示所有图层" +title = "Layers" [viewer.link] delete = "删除链接" @@ -11646,6 +11682,7 @@ exitRedaction = "退出涂黑模式" exportAll = "导出 PDF" exportSelected = "导出所选页面" formFill = "填写表单" +hideToolbar = "Hide toolbar" multiTool = "多功能工具" panMode = "平移模式" print = "打印 PDF" @@ -11666,6 +11703,7 @@ selectAll = "全选" selectByNumber = "按页码选择" selectLanguage = "选择语言" share = "共享" +showToolbar = "Show toolbar" toggleAnnotations = "切换注释显示" toggleAttachments = "切换附件" toggleBookmarks = "切换书签" diff --git a/frontend/editor/public/locales/zh-TW/translation.toml b/frontend/editor/public/locales/zh-TW/translation.toml index 99c96f0ea6..827d088e37 100644 --- a/frontend/editor/public/locales/zh-TW/translation.toml +++ b/frontend/editor/public/locales/zh-TW/translation.toml @@ -1,7 +1,6 @@ alphabet = "字母表" apply = "套用" applyAndContinue = "儲存並離開" -areYouSure = "確定要離開嗎?" back = "返回" black = "黑色" blue = "藍色" @@ -106,6 +105,7 @@ undoSuccess = "已成功復原作業" unknown = "未知" unpin = "取消釘選檔案(工具執行後以新檔取代)" unsavedChanges = "您對 PDF 的變更尚未儲存。" +unsavedChangesBody = "You have unsaved changes to your PDF. Are you sure you want to leave?" unsavedChangesTitle = "未儲存的變更" unsupported = "不支援" username = "使用者名稱" @@ -1879,6 +1879,9 @@ width = "寬度" [app] description = "免費的 Adobe Acrobat 替代方案(下載量 10M+)" +[appBanner] +dismiss = "Dismiss" + [attachments] convertToPdfA3b = "轉換為 PDF/A-3b" convertToPdfA3bDescription = "建立含內嵌附件的封存用 PDF" @@ -2933,12 +2936,16 @@ text = "若要使這些權限無法被變更,請使用「新增密碼」工具 [chat.actions] copy = "複製訊息" +[chat.emptyState] +text = "Ask a question about your documents or get help with PDF tools." + [chat.fab] close = "Close chat" open = "Open Stirling AI assistant" [chat.header] agentMenu = "Stirling 代理選項" +assistant = "AI Assistant" clearChat = "清除聊天" [chat.input] @@ -3001,28 +3008,35 @@ tooltip = "此操作會使用您的雲端點數" tooltip = "從畫面取色" [colorPicker] +hue = "Hue" +saturation = "Saturation and brightness" title = "選擇顏色" [common] +actions = "Actions" back = "返回" cancel = "取消" close = "關閉" +codeSample = "Code sample" collapse = "收合" confirm = "確認" continue = "繼續" copied = "已複製!" copy = "複製" done = "完成" +enterValue = "Enter value" error = "錯誤" expand = "展開" loading = "正在載入..." next = "下一步" +open = "Open" preview = "預覽" previous = "上一步" refresh = "重新整理" remaining = "剩餘" retry = "重試" save = "儲存" +stepOf = "Step {{current}} of {{total}}" [compare] clearSelected = "清除所選" @@ -3185,6 +3199,7 @@ title = "壓縮方法" [compress.settings] desiredSize = "期望檔案大小" desiredSizePlaceholder = "輸入大小" +desiredSizeUnit = "Size unit" [compress.tooltip.description] text = "壓縮是減少檔案大小的簡單方法。選擇『檔案大小』可輸入目標大小,系統會為您調整品質。選擇『品質』可手動設定壓縮強度。" @@ -3387,6 +3402,24 @@ pdfOptions = "PDF 選項" pdfToCbr = "PDF → CBR" pdfToCbz = "PDF → CBZ" pdfToEpub = "PDF → EPUB" +pdfUaAltTextNotice = "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified." +pdfUaAltTextScanFailed = "The images could not be listed. Convert anyway and the response reports what is missing." +pdfUaAltTextSingleFileOnly = "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images." +pdfUaEmbedFonts = "Embed missing fonts" +pdfUaEmbedFontsHelp = "PDF/UA requires every font to be embedded. Turning this off is faster but usually prevents conformance." +pdfUaFigureLabel = "Page {{page}} {{kind}}" +pdfUaFigurePlaceholder = "What this image tells the reader" +pdfUaFindImages = "Find images needing a description" +pdfUaLanguage = "Document language" +pdfUaLanguageHelp = "A BCP-47 tag such as en-GB. Used only when the document does not already declare its own language." +pdfUaNoImagesNeedingText = "No image is missing a description." +pdfUaOptions = "PDF/UA Options" +pdfUaOverrideLanguage = "Replace the document's own language" +pdfUaOverrideLanguageHelp = "Only tick this if the language above is right and the document's own is wrong. Relabelling a document into a language it is not written in makes a screen reader unintelligible." +pdfUaProfile = "Conformance level" +pdfUaSignatureWarning = "This PDF is digitally signed. Tagging rewrites the page content the signature covers, so the signature will stop verifying. Convert first, then re-sign." +pdfUaTitle = "Document title" +pdfUaTitleHelp = "Shown by a reader instead of the filename. Left blank, the first heading is used." selectSourceFormatFirst = "請先選擇來源格式" settings = "設定" single = "單一" @@ -3565,7 +3598,11 @@ tags = "automation,folder,scanning,watch folder,hot folder,automatic processing, tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP" [dropdownList] +noItems = "No items available" +noResults = "No results found" searchPlaceholder = "搜尋..." +selectLanguages = "Select languages" +selectOption = "Select an option" [editableSecretField] edit = "編輯" @@ -3824,11 +3861,13 @@ shareSelected = "共用已選取項" sharing = "共用" showAll = "顯示全部" showHistory = "顯示歷史" +sortBy = "Sort files" sortByDate = "依日期排序" sortByName = "依名稱排序" sortBySize = "依大小排序" storage = "儲存空間" storageState = "儲存狀態" +storageUsed = "Storage used" synced = "已同步" title = "上傳 PDF 檔案" toolChain = "已套用的工具" @@ -3874,8 +3913,12 @@ addFiles = "新增檔案" addingFiles = "正在新增檔案…" collapse = "收合側邊欄" customizeGroups = "自訂群組" +dataLostBody = "This browser lost this file's contents. Upload it again to keep working with it." +dataLostTitle = "File data is unavailable" +downloadFailed = "Download failed" dropHint = "開啟檔案以開始使用" dropToAdd = "Drop files to add" +duplicateFailed = "Could not duplicate file" expand = "展開側邊欄" googleDrive = "Google Drive" googleDriveDisabled = "尚未設定 Google Drive" @@ -3888,15 +3931,17 @@ openFromComputer = "從電腦開啟" openSettings = "開啟設定" other = "其他" recent = "最近" -search = "搜尋" -searchPlaceholder = "搜尋檔案…" viewAll = "檢視全部 {{count}} 個檔案" [fileSidebar.fileItem] closeViewer = "關閉檢視器" +dataLost = "Data lost" +dataLostTooltip = "This browser lost this file's contents. Upload it again to keep working with it." delete = "Delete" +duplicate = "Duplicate" moreActions = "More actions" openInViewer = "在檢視器中開啟" +rename = "Rename" savedToServer = "Saved to server" updateOnServer = "Update on server" uploadToServer = "Upload to server" @@ -3908,6 +3953,14 @@ reset = "重設為預設值" subtitle = "將您的檔案分組到父類別中。將現有或新的標籤新增至類別、重新命名,或建立您自己的類別。不屬於任何類別的檔案會顯示在「其他」下。" title = "側邊欄類別" +[fileSidebar.rename] +cancel = "Cancel" +error = "Could not rename the file." +illegalCharacters = "A file name can't contain \\ / : * ? \" < > |" +label = "File name" +save = "Rename" +title = "Rename file" + [filesPage] addToWorkspace = "加入至工作區" addToWorkspaceCount = "將 {{count}} 個加入工作區" @@ -3918,7 +3971,6 @@ backToMyFiles = "返回我的檔案" breadcrumbs = "資料夾路徑" cancel = "取消" classification = "分類" -clearSearch = "清除搜尋" clearSelection = "清除選取" closeDetails = "關閉詳細資料" create = "建立" @@ -3950,6 +4002,7 @@ downloadAll = "全部下載" downloadVersion = "下載此版本" dropOverlay = "拖放檔案以上傳" dropOverlaySub = "檔案會先新增至本機。使用『移至』或『儲存到雲端』將它們整理到資料夾。" +duplicate = "Duplicate" file = "檔案" fileInfo = "File info" fileMenu = "檔案動作" @@ -3983,8 +4036,6 @@ resizeFolderTree = "調整資料夾樹狀結構大小(方向鍵,按住 Shift save = "儲存" saveToServer = "儲存到伺服器" saveToServerDisabledHint = "此伺服器未啟用儲存到伺服器。請聯絡管理員啟用。" -search = "搜尋" -searchPlaceholder = "搜尋此資料夾與子資料夾" selectAll = "全選" selectAllHint = "按一下以全選。提示:按住 Ctrl(或 Cmd)可逐一新增檔案,按住 Shift 可選取範圍。" selectedCount = "已選取 {{count}} 個" @@ -4044,6 +4095,10 @@ title = "尚無雲端檔案" hint = "未上傳而儲存的檔案會留在此處。拖放檔案以新增。" title = "沒有僅存在本機的檔案" +[filesPage.empty.noResults] +hint = "No files in this folder match your filter. Try a different term or clear the filter." +title = "No matching files" + [filesPage.empty.recent] hint = "您開啟或編輯的檔案會出現在這裡。" title = "尚未有變更" @@ -4063,6 +4118,8 @@ cloudDeleteFailed_one = "Couldn't delete 1 file from the cloud." cloudDeleteFailed_other = "Couldn't delete {{count}} files from the cloud." deleteFolderFailed = "無法刪除資料夾。" deleteFolderFailedDetail = "無法刪除資料夾:{{message}}" +downloadFailed = "Could not download the file." +duplicateFailed = "Could not duplicate the file." folderAppearanceFailed = "無法更新資料夾外觀。" folderAppearanceFailedDetail = "無法更新資料夾外觀:{{message}}" moveFilesFailed = "無法移動檔案。" @@ -4113,6 +4170,11 @@ localHint = "僅儲存在此瀏覽器" shared = "已分享" sharedHint = "透過連結與您分享" +[filesPage.search] +clear = "Clear filter" +label = "Filter files by name" +placeholder = "Filter files…" + [filesPage.sort] modifiedAsc = "由舊到新" modifiedDesc = "最近優先" @@ -4168,6 +4230,7 @@ noFilesInStorageOpen = "儲存空間中沒有可用檔案。請先開啟一些 open = "開啟" openFile = "開啟檔案" openFiles = "開啟多個檔案" +selectFile = "Select {{name}}" selectFromStorage = "從儲存空間選取" upload = "上傳" uploadFile = "上傳檔案" @@ -4241,9 +4304,14 @@ issues = "GitHub" [formFill] allSaved = "全部已儲存" +analyzingFields = "Analysing form fields..." extractCsvError = "擷取 CSV 失敗" extractXlsxError = "擷取 XLSX 失敗" +filled = "filled" flattenAfterFilling = "填寫後扁平化" +noFields = "No fillable form fields found in this PDF." +placeholderEnter = "Enter" +placeholderSelect = "Select" requiredAbbreviation = "必填" requiredFieldsError = "請填寫所有必填欄位" rescanFields = "重新掃描欄位" @@ -4795,9 +4863,6 @@ title = "圖片轉 PDF" [imageToPdf] tags = "轉換,img,jpg,圖片,照片" -[infoBanner] -dismiss = "關閉" - [invite] acceptError = "建立帳號失敗" accountFor = "正在為以下對象建立帳號:" @@ -4979,6 +5044,34 @@ uploadSuccess = "上傳成功!" uploadSuccessMessage = "您的影像已傳送完成。" validating = "正在驗證工作階段…" +[mobileSign] +clear = "Clear" +invalidSession = "Session expired" +invalidSessionMessage = "This QR code is no longer valid. Open the Sign tool on your computer and scan the new code." +penSizeLabel = "Pen size" +send = "Send to computer" +sendAnother = "Send another signature" +sendError = "Could not send the signature. Check the connection and try again." +sentMessage = "Signature sent to your computer. You can send another or close this page." +tabsLabel = "Signature source" +undo = "Undo" +validating = "Checking session…" + +[mobileSign.photo] +fromGallery = "From gallery" +hint = "Photograph a signature on white paper, or choose an existing image." +invalidType = "Please choose an image file." +takePhoto = "Take a photo" + +[mobileSign.tab] +draw = "Draw" +photo = "Photo" +type = "Type" + +[mobileSign.type] +placeholder = "Your name" +previewPlaceholder = "Signature preview" + [mobileUpload] description = "掃描即可上傳相片。影像會自動轉換為 PDF。" descriptionNoConvert = "掃描即可從行動裝置上傳相片。" @@ -4997,8 +5090,13 @@ title = "從行動裝置上傳" tags = "複合工具,多功能,UI,點選拖曳,前端,客戶端,互動,互動式,移動" title = "PDF 複合工具" -[navbar] -search = "搜尋" +[navFooter] +openEditor = "Open PDF Editor" +openProcessor = "Open PDF Processor" + +[navFooter.credits] +count = "{{remaining}} of {{total}}" +label = "Free credits" [oauth.error] message = "驗證未成功。您可以關閉此視窗後再試一次。" @@ -5127,6 +5225,7 @@ activeFiles = "使用中檔案 檢視會顯示您載入工具 allTools = "This is the All Tools panel, where you can browse and select from all available PDF tools." close = "關閉" cropSettings = "選好要裁切的檔案後,我們可以設定裁切工具,選擇要裁切的區域。" +dialogLabel = "Onboarding" fileCheckbox = "點選其中一個檔案即可將其選取以進行處理。您可以選取多個檔案以批次操作。" fileReplacement = "修改後的檔案會自動在工作區取代原檔,讓您能輕鬆再用其他工具處理。" filesButton = "快速存取列上的 檔案 按鈕可讓您上傳 PDF 以使用各種工具。" @@ -5563,6 +5662,7 @@ freeBody = "View, edit, merge, split, sign, watermark, compress, convert and man freeTitle = "Unlimited PDF editing" [payg.free.hero] +barAria = "Free PDFs remaining" capSuffix = "/ {{limit}} free PDFs" metaCategories = "Automation · AI · API requests" @@ -5642,6 +5742,7 @@ automation = "automations" default = "this feature" [payg.spendCapMeter] +barAria = "Spend against cap" capSuffix = "/ {{amount}} cap" metaCategories = "Automation · AI · API spend" resets = "Resets each billing period" @@ -5970,6 +6071,11 @@ header = "PDF 轉 PDF/A" tags = "存檔,長期,標準,轉換,儲存,儲存" title = "PDF 轉 PDF/A" +[pdfToPDFUA] +header = "PDF To PDF/UA" +tags = "accessibility,accessible,tagged,screen reader,wcag,eaa,section 508,conversion" +title = "PDF To PDF/UA" + [pdfToPDFX] tags = "列印,標準,轉換,製作,印前,封存" title = "PDF 轉為 PDF/X" @@ -6045,6 +6151,7 @@ small = "500 點" xsmall = "100 點" [plan.availablePlans] +currency = "Billing currency" subtitle = "選擇符合您需求的方案" title = "可用方案" @@ -6624,6 +6731,7 @@ reachedTitle = "已達每月支出限制" title = "無法開啟 Stripe 入口網站" [portal.billing.walletMeter] +barAria = "Free PDFs remaining" capSuffix_one = "已使用 {{allowance}} 個免費 PDFs" capSuffix_other = "已使用 {{allowance}} 個免費 PDFs" eyebrow = "Processor 試用" @@ -6873,6 +6981,32 @@ label = "Jira" description = "當 policy 執行時,透過 Email 傳送文件或通知。" label = "Mailgun" +[portal.connections.types.n8n] +baseUrlPlaceholder = "https://your-n8n/webhook/9f2c-..." +description = "Hand documents to an n8n workflow, and take back whatever it produces." +label = "n8n" + +[portal.connections.types.n8n.fields.authType] +helperText = "Match the Authentication set on the n8n Webhook node." +label = "Webhook authentication" + +[portal.connections.types.n8n.fields.authType.options.basic] +label = "Basic auth" + +[portal.connections.types.n8n.fields.authType.options.header] +label = "Header auth" + +[portal.connections.types.n8n.fields.authType.options.none] +label = "None" + +[portal.connections.types.n8n.fields.headerName] +helperText = "The header name from the n8n credential, not its value." +label = "Header name" +placeholder = "X-N8N-Key" + +[portal.connections.types.n8n.fields.token] +label = "Header value" + [portal.connections.types.nextcloud] baseUrlPlaceholder = "https://your-server/remote.php/dav" description = "將處理後的文件存入 Nextcloud。" @@ -7077,11 +7211,6 @@ title = "官方 SDKs" beta = "Beta" deprecated = "已淘汰" -[portal.docs.search] -empty = "沒有相符文件" -placeholder = "搜尋文件" -results = "{{count}} 個結果" - [portal.docs.skills] eyebrow = "SKILLS" lead = "代理程式以單一工具呼叫的內建命名能力。每項 skill 都是附有 evals 的確定性操作鏈。" @@ -7180,11 +7309,11 @@ editorAction = "編輯器" empty = "沒有符合此篩選條件的文件。" rowActions = "列動作" sensitiveLabel = "敏感" -sensitiveTitle = "敏感 — 需要存取權" [portal.documents.table.columns] action = "管線 / 動作" document = "文件" +labels = "Labels" product = "產品" status = "狀態" time = "時間" @@ -7200,6 +7329,7 @@ host = "主機" lastSeen = "上次出現" region = "區域" status = "狀態" +target = "Target" version = "版本" [portal.editorAdmin.health.empty] @@ -7274,6 +7404,56 @@ description = "此檢視發生未預期的錯誤。請再試一次,或從側 retry = "再試一次" title = "此頁面發生問題" +[portal.failures] +fromSource = "From source {{source}}" +occurrences = "{{count}} occurrences" +reportedBy = "Hit by {{actor}}" +runReference = "Run {{runId}}" +subtitle = "Failures recorded from your policy runs and your team's editors, with the actions you can take." +title = "Failures" + +[portal.failures.action] +acknowledge = "Acknowledge" +confirm = "Are you sure?" +dismiss = "Dismiss" +dismissSkipFile = "Skip this file" + +[portal.failures.debug] +copyJson = "Copy JSON" +dismissAll = "Dismiss all ({{total}})" +dismissing = "Dismissing..." +hideJson = "Hide raw JSON ({{total}})" +refresh = "Refresh failures" +showJson = "Show raw JSON ({{total}})" + +[portal.failures.disabled] +closed = "This failure is already closed." +unavailable = "Not available for this failure." + +[portal.failures.empty] +description = "Policy runs that fail will appear here with the actions you can take." +title = "No failures recorded" + +[portal.failures.kind.inputPasswordProtected] +description = "The pipeline could not open the document because it is password-protected. Unlock it and run it again, or skip this file." +title = "Password-protected document" + +[portal.failures.kind.unknown] +description = "This run failed for a reason Stirling does not yet recognise. The raw message is shown below." +title = "Unrecognised failure" + +[portal.failures.origin] +pipeline = "Pipeline" +policy = "Policy" +tool = "Tool run" + +[portal.failures.stage] +blocked = "Blocked" +input = "Input" +internal = "Internal" +never_ran = "Never ran" +output = "Output" + [portal.home.download] back = "返回" body = "下載桌面應用程式或自架伺服器。" @@ -7380,11 +7560,6 @@ cancel = "取消" confirm = "撤銷金鑰" title = "撤銷 API 金鑰" -[portal.infrastructure.attestationLabel] -attested = "已證明" -inScope = "範圍內" -notApplicable = "N/A" - [portal.infrastructure.audit] filterAriaLabel = "依類別篩選稽核事件" heading = "稽核記錄" @@ -7456,11 +7631,6 @@ info = "資訊" success = "成功" warning = "警告" -[portal.infrastructure.certLabel] -certified = "已認證" -inProgress = "進行中" -notStarted = "尚未開始" - [portal.infrastructure.createKey] cancel = "取消" createKey = "建立金鑰" @@ -7474,238 +7644,10 @@ subtitleCreated = "立即複製此秘密 — 之後不會再顯示。" title = "建立 API 金鑰" titleCreated = "金鑰已建立" -[portal.infrastructure.deployLabel] -live = "上線" -queued = "已排隊" -rolledBack = "已復原" -rolling = "推出中" - -[portal.infrastructure.deployments] -msValue = "{{value}} ms" -throughputValue = "{{value}}/min" - -[portal.infrastructure.deployments.deployColumns] -deployedBy = "部署者" -environment = "環境" -product = "產品" -status = "狀態" -version = "版本" -when = "時間" - -[portal.infrastructure.deployments.recent] -heading = "最近部署" -subheading = "各產品與環境的最新推出。" - -[portal.infrastructure.deployments.regionColumns] -instances = "執行個體" -latency = "延遲" -load = "負載" -p99 = "P99" -region = "區域" -status = "狀態" -throughput = "輸送量" -uptime = "正常運作時間" -version = "版本" - -[portal.infrastructure.deployments.regions] -heading = "區域" -subheading = "每個已部署 Stirling 區域的即時健康狀態 — 延遲、負載與推出版本。" - -[portal.infrastructure.deployments.regions.empty] -description = "工作區佈建後,已部署區域會顯示在這裡。" -title = "尚未部署區域" - [portal.infrastructure.keyLabel] active = "啟用" revoked = "已撤銷" -[portal.infrastructure.modelLabel] -active = "啟用" -degraded = "效能降低" -disabled = "已停用" - -[portal.infrastructure.models] -heading = "模型" -msValue = "{{value}} ms" -subheading = "支援您工作區文件處理的模型目錄與路由。" - -[portal.infrastructure.models.byom] -description = "註冊 on-prem 或自託管模型,並將其固定到某個區域,以進行受資料駐留限制的處理。" -title = "帶入您自己的模型" - -[portal.infrastructure.models.catalogue] -heading = "目錄" -sub = "工作區可用的代管模型,包含即時延遲與成本。" -subEnterprise = "代管、自備與 on-prem 模型 — 可使用按區域固定。" - -[portal.infrastructure.models.catalogue.empty] -description = "工作區目錄中的模型會顯示在這裡。" -title = "沒有可用模型" - -[portal.infrastructure.models.columns] -cost = "成本" -latency = "延遲" -load = "負載" -model = "模型" -status = "狀態" -type = "類型" -version = "版本" - -[portal.infrastructure.models.cost] -perCall = "{{price}}/次呼叫" -perThousand = "{{price}}/1k" - -[portal.infrastructure.models.metrics] -activeModels = "活躍模型" -avgLatency = "平均延遲" -included = "已包含" -monthlySpend = "每月模型支出" - -[portal.infrastructure.models.routing] -empty = "未設定路由規則。" -heading = "路由規則" -sub = "哪個模型處理每個操作。沒有符合更精確規則時會套用預設值。" -subLocked = "將操作路由到特定模型 — 付費方案可用。" - -[portal.infrastructure.models.routing.lockedBanner] -description = "升級到 Pro 以控制每個操作和文件類型由哪個模型處理。" -title = "模型路由是付費功能" - -[portal.infrastructure.models.routingColumns] -default = "預設" -docType = "文件類型" -modelForAria = "{{operation}} 的模型" -operation = "操作" -routedTo = "路由至" - -[portal.infrastructure.modelTypeLabel] -classification = "分類" -extraction = "擷取" -llm = "LLM" -ocr = "OCR" - -[portal.infrastructure.regionLabel] -degraded = "效能降低" -down = "停止運作" -healthy = "健全" - -[portal.infrastructure.security.access.byok] -description = "提供來自您自己 KMS 的金鑰。Stirling 會使用它加密,但仍可讀取。" -label = "帶入您自己的金鑰 (BYOK)" - -[portal.infrastructure.security.access.hyok] -description = "金鑰永不離開您的 KMS。Stirling 只持有密文。" -label = "保留您自己的金鑰 (HYOK)" - -[portal.infrastructure.security.access.stirling] -description = "Stirling 管理加密金鑰。最簡單 — 您這邊無需金鑰操作。" -label = "Stirling 持有的金鑰" - -[portal.infrastructure.security.accessPolicy] -heading = "文件存取政策" -subheading = "控制誰可以解密靜態儲存的已處理文件。" - -[portal.infrastructure.security.attestations] -heading = "合規性證明" -noReport = "沒有可用報告" -subheading = "逐框架的稽核狀態,並提供已證明控制項的報告。" -viewReport = "檢視報告 →" - -[portal.infrastructure.security.compliance] -heading = "合規性" -subheading = "涵蓋 Stirling 平台的證明與認證。" - -[portal.infrastructure.security.empty] -description = "工作區的安全性設定會顯示在這裡。" -title = "安全狀態無法使用" - -[portal.infrastructure.security.hyokBanner] -description = "使用 HYOK 時,加密金鑰永不離開您的 KMS。Stirling 只儲存和處理您可隨時撤銷的密文。" -title = "Stirling 無法解密您的文件" - -[portal.infrastructure.security.ipAllowlist] -empty = "未設定 IP 範圍 — 允許所有 IP。" -heading = "IP 允許清單" -sub = "API 存取僅限於這些 CIDR 範圍。" -subLocked = "將 API 存取限制為已知 IP 範圍 — 付費方案可用。" - -[portal.infrastructure.security.ipAllowlist.lockedBanner] -description = "升級到 Pro 以將 API 存取限制為特定網路。" -title = "IP 允許清單是付費功能" - -[portal.infrastructure.security.ipColumns] -added = "已新增" -addedBy = "新增者" -cidr = "CIDR" -label = "標籤" - -[portal.infrastructure.security.keyManagement] -algorithm = "演算法" -heading = "加密金鑰管理" -keyId = "金鑰識別碼" -lastRotated = "上次輪換" -rotateKey = "輪換金鑰" -rotationPolicy = "輪換政策" -subheading = "加密靜態儲存文件的金鑰保管 — 誰可以解密,以及金鑰如何輪換。" - -[portal.infrastructure.security.managedBanner] -description = "帶入您自己的金鑰 (BYOK) 和保留您自己的金鑰 (HYOK) 保管可於 Enterprise 使用。升級以提供來自您自己 KMS 的金鑰。" -title = "在您的方案中,金鑰由 Stirling 管理" - -[portal.infrastructure.security.residency.apac] -description = "ap-southeast-1" -label = "亞太地區" - -[portal.infrastructure.security.residency.eu] -description = "eu-west-1 · GDPR 資料邊界" -label = "歐盟" - -[portal.infrastructure.security.residency.us] -description = "us-east-1 · us-west-2" -label = "美國" - -[portal.infrastructure.security.residencyHeader] -heading = "資料駐留" -subheading = "文件儲存與處理的位置。" - -[portal.infrastructure.storage] -gbValue = "{{value}} GB" -percentUsed = "已使用 {{value}}" - -[portal.infrastructure.storage.empty] -description = "已連接的儲存空間和用量會顯示在這裡。" -title = "未設定儲存空間" - -[portal.infrastructure.storage.lifecycle] -active = "啟用中" -activeRange = "0–{{value}}d" -archived = "已封存" -coldStorage = "冷儲存" -deleted = "已刪除" -never = "永不" -purged = "已清除" - -[portal.infrastructure.storage.providers] -connect = "連接" -connected = "已連接" -heading = "已連接提供者" -subheading = "已處理成品寫入的位置。" - -[portal.infrastructure.storage.retention] -heading = "保留" -subheading = "成品在生命週期刪除前保留的時間。" -windowLabel = "預設保留期間" - -[portal.infrastructure.storage.retentionOption] -days_one = "{{count}} 天" -days_other = "{{count}} 天" -never = "永不刪除" - -[portal.infrastructure.storage.totalUsage] -heading = "總用量" -progressLabel = "已使用儲存空間" -subheading = "所有已連接提供者消耗的儲存空間。" - [portal.infrastructure.tabs] apiKeys = "API 金鑰" audit = "稽核記錄" @@ -7715,11 +7657,9 @@ security = "安全性" storage = "儲存空間" [portal.integrations] -addAnother = "新增另一個" availableHeading = "可用" comingSoonHeading = "即將推出" connect = "連線" -connectedHeading = "已連線" connectionCount_one = "{{count}} 個連線" connectionCount_other = "{{count}} 個連線" customApi = "自訂 API" @@ -7737,6 +7677,10 @@ security = "安全性" signing = "簽署" storage = "儲存" +[portal.integrations.noResults] +description = "No integrations match your filters. Try a different category or search." +title = "No matches" + [portal.integrations.status] connected = "已連線" @@ -7767,7 +7711,6 @@ integrations = "整合" pipelines = "管線" policies = "政策" procurement = "採購" -settings = "設定" sources = "來源" usage = "用量與帳單" users = "使用者" @@ -7784,7 +7727,7 @@ title = "管線" newPipeline = "新增管線" [portal.pipelines.builder] -addStep = "新增工具" +activate = "Activate" back = "返回管線" cannotFollow = "無法接收 {{produced}}" chooseAccount = "選擇帳戶" @@ -7792,28 +7735,38 @@ chooseDestination = "選擇目的地" chooseOperation = "選擇此步驟的作用" chooseSource = "選擇來源" discard = "捨棄變更" -enabled = "已啟用" inputs = "輸入" inputSource = "輸入來源" inputTrigger = "觸發器" keepEditing = "繼續編輯" +moreActions = "More actions" needsConfiguring = "需要設定" -needsUpload = "需要上傳的檔案" -noSources = "尚未連線任何來源。建立一個來源以作為輸入。" +needsDestination = "No destination chosen" +needsSource = "No source chosen" noToolMatches = "沒有工具符合您的搜尋。" -pipelineSettings = "管線設定" +pause = "Pause" +rename = "Rename pipeline" searchTools = "搜尋工具" -selectToolBody = "新增工具以建立您的管線。" -selectToolTitle = "尚無工具" sendToSystem = "傳送到另一個系統" stepsIncompatible = "這些步驟無法在其前一步產生的內容上執行:{{tools}}。" stepsNeedSetup = "儲存前仍需設定這些步驟:{{tools}}。" -toolSettings = "工具設定" +supportingFiles = "Supporting files" +testRun = "Test with a file" unknownStep = "無法辨識的操作,已原樣保留。" unsavedBody = "您有未儲存的變更。離開前要儲存,還是捨棄?" unsavedTitle = "未儲存的變更" -uploadUnsupported = "管線尚不支援上傳的檔案,因此無法儲存這些步驟:{{tools}}。" usesDefaults = "使用預設設定執行" +viewDefinition = "View definition" + +[portal.pipelines.builder.blocker] +destination = "Choose a destination" +heading = "To create this pipeline:" +incompatible = "Fix steps that can't run in order: {{tools}}" +name = "Give the pipeline a name" +saveHeading = "To save your changes:" +schedule = "Set how often it runs" +setup = "Finish setting up: {{tools}}" +source = "Choose an input source" [portal.pipelines.builder.diagnostic] fan-in = "合併前一步的每個檔案" @@ -7825,31 +7778,36 @@ undeclared-operation = "無法檢查此步驟接受的內容" [portal.pipelines.composer] addTool = "新增工具" -cancel = "取消" -chainEmpty = "新增工具以開始建立您的管線。" create = "建立管線" +createPaused = "Create paused" editingUnsupported = "尚不支援顯示這些工具參數進行編輯。" -moveDown = "下移" -moveUp = "上移" +editSource = "Edit source" name = "名稱" namePlaceholder = "例如:Redaction sweep" noToolSettings = "此工具沒有可設定的選項。" -operations_one = "操作({{count}})" -operations_other = "操作({{count}})" output = "輸出" -removeStep = "移除操作" save = "儲存變更" scheduleEvery = "執行間隔" -sources = "來源" -sourcesLoading = "正在載入來源..." trigger = "觸發器" triggerManual = "僅手動" +[portal.pipelines.composer.runsEvery] +days_one = "Runs every day" +days_other = "Runs every {{count}} days" +hours_one = "Runs every hour" +hours_other = "Runs every {{count}} hours" +minutes_one = "Runs every minute" +minutes_other = "Runs every {{count}} minutes" + [portal.pipelines.composer.unit] days = "天" hours = "小時" minutes = "分鐘" +[portal.pipelines.definition] +subtitle = "The pipeline as it would be saved." +title = "Definition" + [portal.pipelines.delete] body = "要刪除 \"{{name}}\" 嗎?此操作無法復原。" cancel = "取消" @@ -7867,6 +7825,37 @@ connectSource = "連線來源" description = "建立您的第一個管線:選擇執行來源、串接操作,並選擇輸出位置。" title = "尚無管線" +[portal.pipelines.graph] +addFirstTool = "Add a tool" +dragHint = "Drop on a line to move it" +insertHere = "Add a tool here" +removeNode = "Remove {{name}}" +showError = "Show why {{name}} failed" + +[portal.pipelines.graph.add] +input = "Add a source" +output = "Add a destination" + +[portal.pipelines.graph.run] +done = "Done" +failed = "Failed" +running = "Running" + +[portal.pipelines.inspector] +multipleBody = "Drag any of them onto a line to move them together, or press Delete to remove them." +multipleSelected_one = "{{count}} step selected" +multipleSelected_other = "{{count}} steps selected" +noSelectionBody = "Pick a node in the graph to change what it does." +noSelectionTitle = "Nothing selected" + +[portal.pipelines.inspector.status] +completed_one = "Finished the only step" +completed_other = "Finished all {{count}} steps" +failed_one = "Failed on the only step" +failed_other = "Failed after {{done}} of {{count}} steps" +running_one = "Running the only step" +running_other = "Running step {{done}} of {{count}}" + [portal.pipelines.kpi] active = "啟用中" paused = "已暫停" @@ -7879,7 +7868,6 @@ completed_one = "執行已完成。" completed_other = "全部 {{count}} 次執行已完成。" empty = "沒有可執行的內容:來源沒有要處理的文件。" failed = "執行失敗:{{error}}" -historyCleared = "歷程記錄已清除。下次執行會重新處理目前來源中的所有項目。" inFlight = "沒有新項目可執行:文件仍在從先前的執行中處理。" parked_one = "沒有可執行項目:{{count}} 份文件先前失敗且已暫停。修正原因後,清除歷程記錄以重試。" parked_other = "沒有可執行項目:{{count}} 份文件先前失敗且已暫停。修正原因後,清除歷程記錄以重試。" @@ -7895,6 +7883,7 @@ name = "管線" sources = "來源" status = "狀態" steps = "步驟" +trigger = "Trigger" [portal.pipelines.trigger] folder-watch = "資料夾監看" @@ -8209,6 +8198,24 @@ label = "附加到 Jira 議題" description = "將處理後的文件作為附件傳送。" label = "以電子郵件傳送文件(Mailgun)" +[portal.policies.operations.n8nGate] +description = "Your workflow decides whether the run carries on." +label = "Ask an n8n workflow to approve" +note = "The workflow must reply with {\"approved\": true}. Anything else — including a timeout — stops the run and parks the document." + +[portal.policies.operations.n8nNotify] +description = "Sends the run's details, not the document." +label = "Tell an n8n workflow" + +[portal.policies.operations.n8nSend] +description = "Sends the document and the run's details to your workflow." +label = "Send the document to n8n" + +[portal.policies.operations.n8nTransform] +description = "Your workflow processes the document and the file it returns replaces it." +label = "Process the document in n8n" +note = "Set the Webhook node to respond \"Using Respond to Webhook Node\", and return a file. On the default \"Immediately\" setting n8n replies with an acknowledgement, and that would replace the document." + [portal.policies.operations.nextcloudUpload] description = "將處理後的文件寫入資料夾。" label = "上傳到 Nextcloud" @@ -8683,16 +8690,6 @@ maxed = "已達上限" subtitle = "您的免費試用持續到 {{date}}。不需要卡片。" title = "Enterprise 試用" -[portal.search] -ariaLabel = "搜尋" -placeholder = "搜尋 Stirling — endpoints、pipelines、文件…" - -[portal.search.empty] -noActionsDescription = "快速動作可用後會顯示在這裡。" -noActionsTitle = "沒有快速動作" -noMatches = "找不到「{{query}}」的相符項目" -noMatchesDescription = "請嘗試不同關鍵字或瀏覽目錄。" - [portal.settings.groups] admin = "管理員" @@ -8743,10 +8740,6 @@ cancel = "取消" confirm = "刪除" title = "刪除來源?" -[portal.sources.empty] -description = "連接資料夾(雲端儲存即將推出),讓您的政策有地方擷取文件。" -title = "尚未連接任何來源" - [portal.sources.kpi] inUse = "使用中" total = "連線" @@ -8785,6 +8778,7 @@ unused = "未使用" documents = "文件" source = "來源" status = "狀態" +type = "Type" usedBy = "政策" [portal.sources.types.box] @@ -9980,9 +9974,6 @@ title = "政策與隱私" [settings.preferences] title = "偏好設定" -[settings.search] -placeholder = "搜尋設定頁面..." - [settings.security] description = "更新您的密碼以保護帳號安全。" title = "安全性" @@ -10128,6 +10119,7 @@ last = "最後一頁" maintainRatio = "切換維持長寬比" next = "下一頁" noSavedSigs = "尚未儲存任何簽章" +penSizePlaceholder = "Type or select pen size (1-200)" personalSigs = "個人簽章" previous = "上一頁" redo = "重做" @@ -10166,6 +10158,7 @@ backgroundRemovalFailedTitle = "背景移除失敗" hint = "上傳您的簽名 PNG 或 JPG 影像" label = "上傳簽名影像" placeholder = "選擇影像檔" +previewAlt = "Current image signature" processing = "正在處理影像…" removeBackground = "移除白色背景(設為透明)" @@ -10179,6 +10172,17 @@ saved = "在上方選擇已儲存的簽名,然後在 PDF 任意位置點選以 text = "在上方輸入您的姓名後,於 PDF 任意位置點選以放置簽名。" title = "如何加入簽名" +[sign.mobile] +createFromPhone = "Mobile upload" +description = "Scan this QR code with your phone or tablet, draw your signature, and it will appear here automatically." +error = "Connection Error" +expiryWarning = "QR Code Expiring Soon" +expiryWarningMessage = "This QR code will expire in {{seconds}} seconds. A new code will be generated automatically." +instructions = "Open the camera app on your phone and scan this code. Keep this window open while you draw." +pollingError = "Error checking for the signature" +sessionCreateError = "Failed to create session" +title = "Draw on your phone" + [sign.mode] move = "移動簽名" pause = "暫停放置" @@ -10625,6 +10629,23 @@ title = "上傳至伺服器" updateButton = "更新至伺服器" uploadButton = "上傳至伺服器" +[superSearch] +all = "All" +ariaLabel = "Super search" +filtersAriaLabel = "Search filters" +hint = "Type to search" +placeholder = "Search Stirling" +showLess = "Show less" +showMore = "Show {{count}} more" + +[superSearch.group] +docs = "Docs" +files = "Files" +pages = "Pages" +processor = "Processor" +settings = "Settings" +tools = "Tools" + [survey] title = "Stirling-PDF Survey" @@ -10774,7 +10795,6 @@ goBack = "返回" pdfTools = "PDF 工具" placeholder = "選擇一個工具以開始" premiumFeature = "進階功能:" -searchTools = "搜尋工具" toolsHeader = "工具" viewAllTools = "檢視所有工具" @@ -10822,6 +10842,9 @@ removal = "移除" signing = "簽章" verification = "驗證" +[toolRenderer] +notFound = "Tool not found: {{tool}}" + [tools] noSearchResults = "找不到工具" noTools = "沒有可用的工具" @@ -10979,6 +11002,13 @@ approver = "核准政策" editor = "編輯器" processor = "處理器" +[users.columns] +capabilities = "Capabilities" +email = "Email" +person = "Person" +role = "Role" +status = "Status" + [users.confirm] cancelInviteBody = "要取消對 {{email}} 的邀請嗎?他們將無法使用目前的連結加入。" cancelInviteTitle = "取消邀請" @@ -10998,10 +11028,8 @@ title = "尚無成員" addToTeam = "新增至團隊" guestCount = "{{count}} 位訪客" guests = "訪客" -guestsDesc = "外部協作者,範圍限於您分享的內容。僅限編輯器。" ledBy = "由 {{owner}} 領導" org = "組織" -orgDesc = "擁有組織範圍權限與政策核准權的擁有者" owners = "{{count}} 位擁有者" team = "{{name}} 團隊" teamMeta = "{{count}} 人" @@ -11041,13 +11069,15 @@ usernamePlaceholder = "jsmith" [users.invites] by = "由 {{who}} 邀請" cancel = "取消" -count = "{{count}} 個待處理" -desc = "已受邀但尚未加入的人員。在他們接受前會佔用一個席位。" expiresInDays_one = "{{count}} 天後到期" expiresInDays_other = "{{count}} 天後到期" expiresToday = "今天到期" title = "待處理邀請" +[users.invites.columns] +expires = "Expires" +invitee = "Invitee" + [users.loadError] description = "連線至後端時發生錯誤,或您沒有存取權限。請再試一次。" title = "無法載入成員" @@ -11237,12 +11267,15 @@ searchPlaceholder = "搜尋附件" title = "附件" [viewer.bookmarks] +addBookmark = "Add bookmark" bookmarkTitle = "書籤標題" bookmarkTitleRequired = "必須輸入書籤標題" closeSidebar = "關閉書籤側邊欄" collapseAll = "收合所有書籤" +empty = "No bookmarks in this document" expandAll = "展開所有書籤" searchPlaceholder = "搜尋書籤" +title = "Bookmarks" [viewer.comments] addComment = "新增評論" @@ -11264,10 +11297,12 @@ locateAnnotation = "在文件中定位" moreActions = "更多動作" nComments_one = "{{count}} comment" nComments_other = "{{count}} comments" +noMatch = "No comments match your search" pageLabel = "第 {{page}} 頁" placingHint = "按一下頁面以放置…(取消)" removeCommentOnly = "僅移除評論" saveReply = "儲存回覆" +searchPlaceholder = "Search comments" title = "評論" typeComment = "評論" typeInsertText = "插入文字" @@ -11290,6 +11325,7 @@ unsavedDesc = "您有未儲存的變更" closeSidebar = "關閉圖層側邊欄" hideAll = "隱藏所有圖層" showAll = "顯示所有圖層" +title = "Layers" [viewer.link] delete = "刪除連結" @@ -11646,6 +11682,7 @@ exitRedaction = "離開遮蔽模式" exportAll = "匯出 PDF" exportSelected = "匯出所選頁面" formFill = "填寫表單" +hideToolbar = "Hide toolbar" multiTool = "多功能工具" panMode = "平移模式" print = "列印 PDF" @@ -11666,6 +11703,7 @@ selectAll = "全選" selectByNumber = "依頁碼選取" selectLanguage = "選擇語言" share = "分享" +showToolbar = "Show toolbar" toggleAnnotations = "切換註解顯示" toggleAttachments = "切換附件" toggleBookmarks = "切換書籤" diff --git a/frontend/editor/scripts/generate-tool-api-types.mts b/frontend/editor/scripts/generate-tool-api-types.mts index d5990096af..38eb7fe7b6 100644 --- a/frontend/editor/scripts/generate-tool-api-types.mts +++ b/frontend/editor/scripts/generate-tool-api-types.mts @@ -28,10 +28,11 @@ const ALLOWED_PATH_PREFIXES = [ "/api/v1/integration/", ]; -// File plumbing, not user parameters: `fileInput` is the uploaded document and -// `fileId` a server-side handle. Stripped from every generated request model. -// Named file fields (stampImage, attachments, ...) are real parameters and kept. -const BASE_FILE_FIELDS = new Set(["fileInput", "fileId"]); +// File plumbing, not user parameters: `fileInput` and `file` are the uploaded primary document +// (endpoints use one name or the other - `file` is never a second, supporting upload) and `fileId` +// a server-side handle. Stripped from every generated request model. Named supporting-file fields +// (stampImage, attachments, ...) are real parameters and kept. +const BASE_FILE_FIELDS = new Set(["fileInput", "file", "fileId"]); // The shared "upload a file or provide a file ID" wrapper schema and its two // branches. An endpoint whose body is exactly this has no parameters, so it must @@ -73,6 +74,19 @@ function isObject(value: unknown): value is Json { return typeof value === "object" && value !== null && !Array.isArray(value); } +/** A single file upload: `type: string, format: binary` (a Java MultipartFile param). */ +function isBinaryField(schema: unknown): schema is Json { + return ( + isObject(schema) && schema.type === "string" && schema.format === "binary" + ); +} + +/** A multi file upload: an array of binary items (some specs also flag the array itself binary). */ +function isBinaryArrayField(schema: unknown): schema is Json { + if (!isObject(schema) || schema.type !== "array") return false; + return schema.format === "binary" || isBinaryField(schema.items); +} + /** * Recursively sort object keys so the output is byte-stable regardless of the * key ordering springdoc happens to emit. @@ -358,6 +372,9 @@ async function main(): Promise { const usedClassNames = new Set(); const pendingComponents = new Set(); const skipped: string[] = []; + // Named file fields (as File uploads) per model, so a caller can tell a file param from a scalar + // string param - which `format: binary` -> `string` would otherwise erase. + const fileFieldsByClass: Record = {}; for (const path of Object.keys(paths).sort()) { if ( @@ -408,7 +425,32 @@ async function main(): Promise { const query = queryParameters(pathItem); // Body wins over query on a name collision. const properties: Json = { ...query.props, ...bodyProps }; + // `file` is stripped as a primary-document alias (see BASE_FILE_FIELDS). That only holds while + // no endpoint uses `file` as a *supporting* upload beside a primary `fileInput`; if one ever + // does, blanket-stripping would silently drop it. Fail generation so the assumption is fixed + // here rather than shipping a lost file. + if ("file" in properties && "fileInput" in properties) { + throw new Error( + `${path} has both 'fileInput' and 'file' uploads. 'file' is stripped as a primary-document` + + " alias, which would drop it as a supporting file. Rename the supporting param or revise" + + " BASE_FILE_FIELDS handling in this generator.", + ); + } for (const field of BASE_FILE_FIELDS) delete properties[field]; + // Type each named file upload as File/File[] (not the `string` a binary format yields) via + // json-schema-to-typescript's `tsType` override, and record it. Base file fields are already + // stripped, so what remains is the real supporting-file params. + const fileFields: string[] = []; + for (const [name, prop] of Object.entries(properties)) { + if (isBinaryField(prop)) { + prop.tsType = "File"; + fileFields.push(name); + } else if (isBinaryArrayField(prop)) { + prop.tsType = "File[]"; + fileFields.push(name); + } + } + fileFieldsByClass[className] = fileFields; modelSchema.properties = properties; const required = new Set(computeRequired(modelSchema, properties)); for (const name of query.required) { @@ -464,6 +506,7 @@ async function main(): Promise { await compileAndWrite( tools, definitions, + fileFieldsByClass, outputPath, values.check ?? false, skipped, @@ -473,6 +516,7 @@ async function main(): Promise { async function compileAndWrite( tools: DiscoveredTool[], definitions: Record, + fileFieldsByClass: Record, outputPath: string, check: boolean, skipped: string[], @@ -525,6 +569,15 @@ async function compileAndWrite( const endpointList = tools .map((t) => ` ${JSON.stringify(t.path)},`) .join("\n"); + // Endpoints that take supporting files, mapped to those file params' names. Only endpoints with at + // least one are listed, so membership answers "does this tool take extra files". + const fileFieldEntries = tools + .filter((t) => (fileFieldsByClass[t.className] ?? []).length > 0) + .map( + (t) => + ` ${JSON.stringify(t.path)}: ${JSON.stringify(fileFieldsByClass[t.className])},`, + ) + .join("\n"); const footer = [ "/** Endpoint path for a generated tool operation (the operation identity across languages). */", @@ -536,6 +589,9 @@ async function compileAndWrite( "/** Every generated tool endpoint, for iteration. */", `export const TOOL_ENDPOINTS = [\n${endpointList}\n] as const satisfies readonly ToolEndpoint[];`, "", + "/** The supporting-file parameters each endpoint accepts beyond its primary fileInput, by name. */", + `export const TOOL_FILE_FIELDS = {\n${fileFieldEntries}\n} as const satisfies Partial<\n Record\n>;`, + "", "/** Union of every generated tool request model. */", `export type ToolApiRequest = ToolApiParams[ToolEndpoint];`, ].join("\n"); diff --git a/frontend/editor/src-tauri/icons/128x128.png b/frontend/editor/src-tauri/icons/128x128.png deleted file mode 100644 index 9712693d06..0000000000 Binary files a/frontend/editor/src-tauri/icons/128x128.png and /dev/null differ diff --git a/frontend/editor/src-tauri/icons/16x16.png b/frontend/editor/src-tauri/icons/16x16.png deleted file mode 100644 index 6448ce5424..0000000000 Binary files a/frontend/editor/src-tauri/icons/16x16.png and /dev/null differ diff --git a/frontend/editor/src-tauri/icons/192x192.png b/frontend/editor/src-tauri/icons/192x192.png deleted file mode 100644 index 55c165df4b..0000000000 Binary files a/frontend/editor/src-tauri/icons/192x192.png and /dev/null differ diff --git a/frontend/editor/src-tauri/icons/32x32.png b/frontend/editor/src-tauri/icons/32x32.png deleted file mode 100644 index 1844dfceb2..0000000000 Binary files a/frontend/editor/src-tauri/icons/32x32.png and /dev/null differ diff --git a/frontend/editor/src-tauri/icons/64x64.png b/frontend/editor/src-tauri/icons/64x64.png deleted file mode 100644 index d758a26bf3..0000000000 Binary files a/frontend/editor/src-tauri/icons/64x64.png and /dev/null differ diff --git a/frontend/editor/src-tauri/icons/icon.icns b/frontend/editor/src-tauri/icons/icon.icns deleted file mode 100644 index 86bad6a2e1..0000000000 Binary files a/frontend/editor/src-tauri/icons/icon.icns and /dev/null differ diff --git a/frontend/editor/src-tauri/icons/icon.png b/frontend/editor/src-tauri/icons/icon.png deleted file mode 100644 index 8abdd8ca3a..0000000000 Binary files a/frontend/editor/src-tauri/icons/icon.png and /dev/null differ diff --git a/frontend/editor/src-tauri/icons/linux/app-128.png b/frontend/editor/src-tauri/icons/linux/app-128.png new file mode 100644 index 0000000000..9a87d8abb3 Binary files /dev/null and b/frontend/editor/src-tauri/icons/linux/app-128.png differ diff --git a/frontend/editor/src-tauri/icons/linux/app-16.png b/frontend/editor/src-tauri/icons/linux/app-16.png new file mode 100644 index 0000000000..35fc507cfc Binary files /dev/null and b/frontend/editor/src-tauri/icons/linux/app-16.png differ diff --git a/frontend/editor/src-tauri/icons/linux/app-192.png b/frontend/editor/src-tauri/icons/linux/app-192.png new file mode 100644 index 0000000000..36fd70e987 Binary files /dev/null and b/frontend/editor/src-tauri/icons/linux/app-192.png differ diff --git a/frontend/editor/src-tauri/icons/linux/app-32.png b/frontend/editor/src-tauri/icons/linux/app-32.png new file mode 100644 index 0000000000..c975d69987 Binary files /dev/null and b/frontend/editor/src-tauri/icons/linux/app-32.png differ diff --git a/frontend/editor/src-tauri/icons/linux/app-512.png b/frontend/editor/src-tauri/icons/linux/app-512.png new file mode 100644 index 0000000000..b20a797a05 Binary files /dev/null and b/frontend/editor/src-tauri/icons/linux/app-512.png differ diff --git a/frontend/editor/src-tauri/icons/linux/app-64.png b/frontend/editor/src-tauri/icons/linux/app-64.png new file mode 100644 index 0000000000..e6138f71e3 Binary files /dev/null and b/frontend/editor/src-tauri/icons/linux/app-64.png differ diff --git a/frontend/editor/src-tauri/icons/macos/app-512.png b/frontend/editor/src-tauri/icons/macos/app-512.png new file mode 100644 index 0000000000..c5638a5270 Binary files /dev/null and b/frontend/editor/src-tauri/icons/macos/app-512.png differ diff --git a/frontend/editor/src-tauri/icons/macos/app.icns b/frontend/editor/src-tauri/icons/macos/app.icns new file mode 100644 index 0000000000..7f6dc2c6e8 Binary files /dev/null and b/frontend/editor/src-tauri/icons/macos/app.icns differ diff --git a/frontend/editor/src-tauri/icons/icon.ico b/frontend/editor/src-tauri/icons/windows/app.ico similarity index 100% rename from frontend/editor/src-tauri/icons/icon.ico rename to frontend/editor/src-tauri/icons/windows/app.ico diff --git a/frontend/editor/src-tauri/tauri.conf.json b/frontend/editor/src-tauri/tauri.conf.json index dee2cc7023..cdd29e708f 100644 --- a/frontend/editor/src-tauri/tauri.conf.json +++ b/frontend/editor/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Stirling PDF", "mainBinaryName": "Stirling-PDF", - "version": "2.14.2", + "version": "2.14.3", "identifier": "stirling.pdf.dev", "build": { "frontendDist": "../dist", @@ -29,14 +29,9 @@ "publisher": "Stirling PDF Inc.", "targets": ["deb", "rpm", "appimage", "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" + "icons/windows/app.ico", + "icons/macos/app.icns", + "icons/macos/app-512.png" ], "resources": ["libs/*.jar", "runtime/jre/**/*"], "fileAssociations": [ diff --git a/frontend/editor/src-tauri/tauri.linux.conf.json b/frontend/editor/src-tauri/tauri.linux.conf.json new file mode 100644 index 0000000000..54f87a037b --- /dev/null +++ b/frontend/editor/src-tauri/tauri.linux.conf.json @@ -0,0 +1,12 @@ +{ + "bundle": { + "icon": [ + "icons/linux/app-512.png", + "icons/linux/app-192.png", + "icons/linux/app-128.png", + "icons/linux/app-64.png", + "icons/linux/app-32.png", + "icons/linux/app-16.png" + ] + } +} diff --git a/frontend/editor/src/assets/3rdPartyLicenses.json b/frontend/editor/src/assets/3rdPartyLicenses.json index 110a6c88a5..83d9af793b 100644 --- a/frontend/editor/src/assets/3rdPartyLicenses.json +++ b/frontend/editor/src/assets/3rdPartyLicenses.json @@ -10,7 +10,7 @@ { "moduleName": "@cantoo/pdf-lib", "moduleUrl": "https://github.com/cantoo-scribe/pdf-lib", - "moduleVersion": "2.6.5", + "moduleVersion": "2.8.2", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, @@ -24,161 +24,161 @@ { "moduleName": "@embedpdf/core", "moduleUrl": "https://registry.npmjs.org/@embedpdf/core/-/core-2.15.0.tgz", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/engines", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/models", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-annotation", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-attachment", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-bookmark", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-document-manager", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-export", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-history", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-interaction-manager", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-pan", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-print", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-redaction", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-render", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-rotate", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-scroll", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-search", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-selection", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-spread", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-thumbnail", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-tiling", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-viewport", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@embedpdf/plugin-zoom", "moduleUrl": "https://github.com/embedpdf/embed-pdf-viewer", - "moduleVersion": "2.14.4", + "moduleVersion": "2.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, @@ -255,14 +255,14 @@ { "moduleName": "@stripe/react-stripe-js", "moduleUrl": "https://github.com/stripe/react-stripe-js", - "moduleVersion": "4.0.2", + "moduleVersion": "6.8.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, { "moduleName": "@stripe/stripe-js", "moduleUrl": "https://github.com/stripe/stripe-js", - "moduleVersion": "7.9.0", + "moduleVersion": "9.10.0", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, @@ -287,6 +287,13 @@ "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, + { + "moduleName": "@tanstack/react-table", + "moduleUrl": "https://github.com/TanStack/table", + "moduleVersion": "9.1.2", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://opensource.org/licenses/MIT" + }, { "moduleName": "@tanstack/react-virtual", "moduleUrl": "https://github.com/TanStack/virtual", @@ -367,7 +374,7 @@ { "moduleName": "i18next", "moduleUrl": "https://github.com/i18next/i18next", - "moduleVersion": "25.10.10", + "moduleVersion": "26.3.6", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, @@ -451,7 +458,7 @@ { "moduleName": "react-i18next", "moduleUrl": "https://github.com/i18next/react-i18next", - "moduleVersion": "16.6.6", + "moduleVersion": "17.0.11", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, diff --git a/frontend/editor/src/core/components/AppLayout.tsx b/frontend/editor/src/core/components/AppLayout.tsx index 38ab1f1907..48c4cb9149 100644 --- a/frontend/editor/src/core/components/AppLayout.tsx +++ b/frontend/editor/src/core/components/AppLayout.tsx @@ -22,7 +22,7 @@ export function AppLayout({ children }: AppLayoutProps) { } `}
{banner}
{children}
diff --git a/frontend/editor/src/core/components/filesPage/FileGrid.tsx b/frontend/editor/src/core/components/filesPage/FileGrid.tsx index e54964c592..40e17dc502 100644 --- a/frontend/editor/src/core/components/filesPage/FileGrid.tsx +++ b/frontend/editor/src/core/components/filesPage/FileGrid.tsx @@ -13,6 +13,7 @@ import DeleteIcon from "@mui/icons-material/Delete"; import HistoryIcon from "@mui/icons-material/History"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import DriveFileRenameOutlineIcon from "@mui/icons-material/DriveFileRenameOutline"; +import ContentCopyOutlinedIcon from "@mui/icons-material/ContentCopyOutlined"; import CloudUploadIcon from "@mui/icons-material/CloudUpload"; import UploadFileIcon from "@mui/icons-material/UploadFile"; import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder"; @@ -36,6 +37,8 @@ import { FolderThumbnail } from "@app/components/filesPage/FolderThumbnail"; import { findFolderIcon } from "@app/components/filesPage/folderIcons"; import { FolderAppearancePicker } from "@app/components/filesPage/FolderAppearancePicker"; import { useLazyThumbnail } from "@app/hooks/useLazyThumbnail"; +import { useFileActionIcons } from "@app/hooks/useFileActionIcons"; +import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology"; import type { FilesPageSortMode } from "@app/contexts/FilesPageContext"; import { OpenInNewWindowMenuItem } from "@app/components/filesPage/OpenInNewWindowMenuItem"; @@ -83,6 +86,12 @@ interface FileGridProps { onSaveToServer?: (file: StirlingFileStub) => void; /** Open the version-history modal for a file (only when it has >1 version). */ onVersionHistory?: (file: StirlingFileStub) => void; + /** Download a copy (desktop: save a copy). */ + onDownloadFile?: (file: StirlingFileStub) => void; + /** Open the rename dialog for a file. */ + onRenameFile?: (file: StirlingFileStub) => void; + /** Save a second copy of the file into the library. */ + onDuplicateFile?: (file: StirlingFileStub) => void; /** When set, the Save to server item renders disabled with this tooltip. */ saveToServerDisabledReason?: string | null; /** When supplied the list-view column headers become sortable. */ @@ -366,24 +375,21 @@ function EmptyState({ ); } -function GridView({ - entries, - selectedFileIds, - activeWorkspaceFileIds, - onSelectFile, - onOpenFolder, - onOpenFile, - onMoveFiles, - onMoveFolder, - onRenameFolder, - onDeleteFolder, - onChangeFolderAppearance, - onRemoveFiles, - onPromptMoveFiles, - onSaveToServer, - onVersionHistory, - saveToServerDisabledReason, -}: FileGridProps) { +function GridView(props: FileGridProps) { + const { + entries, + selectedFileIds, + activeWorkspaceFileIds, + onSelectFile, + onOpenFolder, + onOpenFile, + onMoveFiles, + onMoveFolder, + onRenameFolder, + onDeleteFolder, + onChangeFolderAppearance, + } = props; + const menuHandlersFor = useFileMenuHandlers(props); return (
{entries.map((entry) => { @@ -424,22 +430,7 @@ function GridView({ onSelectFile(entry.file!.id, e.shiftKey, e.metaKey || e.ctrlKey) } onDoubleClick={() => onOpenFile(entry.file!)} - onRemove={() => onRemoveFiles([entry.file!.id])} - onMove={() => { - const target = selectedFileIds.has(entry.file!.id) - ? Array.from(selectedFileIds) - : [entry.file!.id]; - onPromptMoveFiles(target); - }} - onSaveToServer={ - onSaveToServer ? () => onSaveToServer(entry.file!) : undefined - } - onVersionHistory={ - onVersionHistory - ? () => onVersionHistory(entry.file!) - : undefined - } - saveToServerDisabledReason={saveToServerDisabledReason} + {...menuHandlersFor(entry.file)} /> ); } @@ -626,7 +617,222 @@ function PolicyBadges({ fileId }: { fileId: string }) { return ; } -interface FileCardProps { +/** Per-file actions. Shared verbatim by the grid card and the list row, and + * kept in step with the file sidebar's kebab so both surfaces offer the same. */ +interface FileActionsMenuProps { + file: StirlingFileStub; + triggerRef: React.RefObject; + onOpen: () => void; + onMove: () => void; + onRemove: () => void; + onDownload?: () => void; + onRename?: () => void; + onDuplicate?: () => void; + onSaveToServer?: () => void; + onVersionHistory?: () => void; + saveToServerDisabledReason?: string | null; +} + +function FileActionsMenu({ + file, + triggerRef, + onOpen, + onMove, + onRemove, + onDownload, + onRename, + onDuplicate, + onSaveToServer, + onVersionHistory, + saveToServerDisabledReason, +}: FileActionsMenuProps) { + const { t } = useTranslation(); + const terminology = useFileActionTerminology(); + const DownloadIcon = useFileActionIcons().download; + const showSaveToServer = + Boolean(onSaveToServer) && file.remoteStorageId == null; + const showVersionHistory = + Boolean(onVersionHistory) && (file.versionNumber ?? 1) > 1; + return ( + + + e.stopPropagation()} + aria-label={t("filesPage.fileMenu", "File actions")} + data-testid="file-card-actions" + > + + + + + } + onClick={(e) => { + e.stopPropagation(); + onOpen(); + }} + > + {t("filesPage.addToWorkspace", "Add to workspace")} + + + } + onClick={(e) => { + e.stopPropagation(); + onMove(); + }} + data-testid="file-menu-move-to" + > + {t("filesPage.moveTo", "Move to…")} + + + {(onDownload || onRename || onDuplicate) && } + {onDownload && ( + } + onClick={(e) => { + e.stopPropagation(); + onDownload(); + }} + data-testid="file-menu-download" + > + {terminology.download} + + )} + {onRename && ( + } + onClick={(e) => { + e.stopPropagation(); + onRename(); + }} + data-testid="file-menu-rename" + > + {t("filesPage.rename", "Rename")} + + )} + {onDuplicate && ( + } + onClick={(e) => { + e.stopPropagation(); + onDuplicate(); + }} + data-testid="file-menu-duplicate" + > + {t("filesPage.duplicate", "Duplicate")} + + )} + + {(showSaveToServer || showVersionHistory) && } + {/* Per-file Save to server; shown for local-only files. When + storage is off it stays visible but disabled with a tooltip. */} + {showSaveToServer && onSaveToServer && ( + + } + disabled={Boolean(saveToServerDisabledReason)} + onClick={(e) => { + e.stopPropagation(); + onSaveToServer(); + }} + style={ + saveToServerDisabledReason + ? { pointerEvents: "auto" } + : undefined + } + > + {t("filesPage.saveToServer", "Save to server")} + + + )} + {showVersionHistory && onVersionHistory && ( + } + onClick={(e) => { + e.stopPropagation(); + onVersionHistory(); + }} + > + {t("filesPage.versionHistory", "Version history")} + + )} + + + } + onClick={(e) => { + e.stopPropagation(); + onRemove(); + }} + > + {t("filesPage.remove", "Delete")} + + + + ); +} + +/** Binds one file's kebab handlers, so grid and list wire them identically. */ +function useFileMenuHandlers( + props: FileGridProps, +): (file: StirlingFileStub) => FileMenuHandlers { + const { + selectedFileIds, + onRemoveFiles, + onPromptMoveFiles, + onSaveToServer, + onVersionHistory, + onDownloadFile, + onRenameFile, + onDuplicateFile, + saveToServerDisabledReason, + } = props; + return (file: StirlingFileStub) => ({ + onRemove: () => onRemoveFiles([file.id]), + // A move acts on the whole selection when this file is part of it. + onMove: () => + onPromptMoveFiles( + selectedFileIds.has(file.id) ? Array.from(selectedFileIds) : [file.id], + ), + onDownload: onDownloadFile ? () => onDownloadFile(file) : undefined, + onRename: onRenameFile ? () => onRenameFile(file) : undefined, + onDuplicate: onDuplicateFile ? () => onDuplicateFile(file) : undefined, + onSaveToServer: onSaveToServer ? () => onSaveToServer(file) : undefined, + onVersionHistory: onVersionHistory + ? () => onVersionHistory(file) + : undefined, + saveToServerDisabledReason, + }); +} + +/** Per-file kebab handlers, shared by the card and row wrappers. */ +interface FileMenuHandlers { + onRemove: () => void; + onMove: () => void; + onDownload?: () => void; + onRename?: () => void; + onDuplicate?: () => void; + /** Kebab Save to server; only fires when file is local-only. */ + onSaveToServer?: () => void; + /** Open the version-history modal; shown only when file has >1 version. */ + onVersionHistory?: () => void; + /** When set, the kebab Save to server is disabled with this tooltip. */ + saveToServerDisabledReason?: string | null; +} + +interface FileCardProps extends FileMenuHandlers { file: StirlingFileStub; isSelected: boolean; isInWorkspace: boolean; @@ -637,14 +843,6 @@ interface FileCardProps { multiSelectActive: boolean; onClick: (e: React.MouseEvent) => void; onDoubleClick: () => void; - onRemove: () => void; - onMove: () => void; - /** Kebab Save to server; only fires when file is local-only. */ - onSaveToServer?: () => void; - /** Open the version-history modal; shown only when file has >1 version. */ - onVersionHistory?: () => void; - /** When set, the kebab Save to server is disabled with this tooltip. */ - saveToServerDisabledReason?: string | null; } function FileCard({ @@ -656,11 +854,7 @@ function FileCard({ multiSelectActive, onClick, onDoubleClick, - onRemove, - onMove, - onSaveToServer, - onVersionHistory, - saveToServerDisabledReason, + ...menuHandlers }: FileCardProps) { const { t } = useTranslation(); const cardRef = useRef(null); @@ -784,126 +978,48 @@ function FileCard({ )}
{fileSize} - · + {fileDate}
- - - e.stopPropagation()} - aria-label={t("filesPage.fileMenu", "File actions")} - data-testid="file-card-actions" - > - - - - - } - onClick={(e) => { - e.stopPropagation(); - onDoubleClick(); - }} - > - {t("filesPage.addToWorkspace", "Add to workspace")} - - - } - onClick={(e) => { - e.stopPropagation(); - onMove(); - }} - data-testid="file-menu-move-to" - > - {t("filesPage.moveTo", "Move to…")} - - {/* Per-file Save to server; shown for local-only files. When - storage is off it stays visible but disabled with a tooltip. */} - {onSaveToServer && file.remoteStorageId == null && ( - - } - disabled={Boolean(saveToServerDisabledReason)} - onClick={(e) => { - e.stopPropagation(); - onSaveToServer(); - }} - style={ - saveToServerDisabledReason - ? { pointerEvents: "auto" } - : undefined - } - > - {t("filesPage.saveToServer", "Save to server")} - - - )} - {onVersionHistory && (file.versionNumber ?? 1) > 1 && ( - } - onClick={(e) => { - e.stopPropagation(); - onVersionHistory(); - }} - > - {t("filesPage.versionHistory", "Version history")} - - )} - - } - onClick={(e) => { - e.stopPropagation(); - onRemove(); - }} - > - {t("filesPage.remove", "Delete")} - - - +
); } -function ListView({ - entries, - selectedFileIds, - activeWorkspaceFileIds, - onSelectFile, - onSetSelection, - onOpenFolder, - onOpenFile, - onMoveFiles, - onMoveFolder, - onRenameFolder, - onDeleteFolder, - onSaveToServer, - onVersionHistory, - saveToServerDisabledReason, - onChangeFolderAppearance, - onRemoveFiles, - onPromptMoveFiles, - sortMode, - onChangeSortMode, -}: FileGridProps & { - sortMode?: FilesPageSortMode; - onChangeSortMode?: (next: FilesPageSortMode) => void; -}) { +function ListView( + props: FileGridProps & { + sortMode?: FilesPageSortMode; + onChangeSortMode?: (next: FilesPageSortMode) => void; + }, +) { + const { + entries, + selectedFileIds, + activeWorkspaceFileIds, + onSelectFile, + onSetSelection, + onOpenFolder, + onOpenFile, + onMoveFiles, + onMoveFolder, + onRenameFolder, + onDeleteFolder, + onChangeFolderAppearance, + sortMode, + onChangeSortMode, + } = props; + const menuHandlersFor = useFileMenuHandlers(props); const { t } = useTranslation(); // Tri-state header checkbox state - computed from current entries. @@ -1029,22 +1145,7 @@ function ListView({ onSelectFile(entry.file!.id, e.shiftKey, e.metaKey || e.ctrlKey) } onOpen={() => onOpenFile(entry.file!)} - onRemove={() => onRemoveFiles([entry.file!.id])} - onMove={() => { - const target = selectedFileIds.has(entry.file!.id) - ? Array.from(selectedFileIds) - : [entry.file!.id]; - onPromptMoveFiles(target); - }} - onSaveToServer={ - onSaveToServer ? () => onSaveToServer(entry.file!) : undefined - } - onVersionHistory={ - onVersionHistory - ? () => onVersionHistory(entry.file!) - : undefined - } - saveToServerDisabledReason={saveToServerDisabledReason} + {...menuHandlersFor(entry.file)} /> ); } @@ -1241,7 +1342,7 @@ function FolderRow({ ); } -interface FileRowProps { +interface FileRowProps extends FileMenuHandlers { file: StirlingFileStub; isSelected: boolean; isInWorkspace: boolean; @@ -1251,14 +1352,6 @@ interface FileRowProps { multiSelectActive: boolean; onClick: (e: React.MouseEvent) => void; onOpen: () => void; - onRemove: () => void; - onMove: () => void; - /** Kebab Save to server; only fires when file is local-only. */ - onSaveToServer?: () => void; - /** Open the version-history modal; shown only when file has >1 version. */ - onVersionHistory?: () => void; - /** When set, the kebab Save to server is disabled with this tooltip. */ - saveToServerDisabledReason?: string | null; } function FileRow({ @@ -1270,11 +1363,7 @@ function FileRow({ multiSelectActive, onClick, onOpen, - onRemove, - onMove, - onSaveToServer, - onVersionHistory, - saveToServerDisabledReason, + ...menuHandlers }: FileRowProps) { const { t } = useTranslation(); const kebabRef = useRef(null); @@ -1414,91 +1503,12 @@ function FileRow({ {fileSize} {fileDate} - - - e.stopPropagation()} - aria-label={t("filesPage.fileMenu", "File actions")} - data-testid="file-card-actions" - > - - - - - } - onClick={(e) => { - e.stopPropagation(); - onOpen(); - }} - > - {t("filesPage.addToWorkspace", "Add to workspace")} - - - } - onClick={(e) => { - e.stopPropagation(); - onMove(); - }} - > - {t("filesPage.moveTo", "Move to…")} - - {/* Per-file Save to server; shown for local-only files. When - storage is off it stays visible but disabled with a tooltip. */} - {onSaveToServer && file.remoteStorageId == null && ( - - } - disabled={Boolean(saveToServerDisabledReason)} - onClick={(e) => { - e.stopPropagation(); - onSaveToServer(); - }} - style={ - saveToServerDisabledReason - ? { pointerEvents: "auto" } - : undefined - } - > - {t("filesPage.saveToServer", "Save to server")} - - - )} - {onVersionHistory && (file.versionNumber ?? 1) > 1 && ( - } - onClick={(e) => { - e.stopPropagation(); - onVersionHistory(); - }} - > - {t("filesPage.versionHistory", "Version history")} - - )} - - } - onClick={(e) => { - e.stopPropagation(); - onRemove(); - }} - > - {t("filesPage.remove", "Delete")} - - - + ); diff --git a/frontend/editor/src/core/components/filesPage/FileManagerView.tsx b/frontend/editor/src/core/components/filesPage/FileManagerView.tsx index a0b0c6cfa1..36ffb9c4dc 100644 --- a/frontend/editor/src/core/components/filesPage/FileManagerView.tsx +++ b/frontend/editor/src/core/components/filesPage/FileManagerView.tsx @@ -33,6 +33,10 @@ import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import CloudUploadIcon from "@mui/icons-material/CloudUpload"; import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight"; import RefreshIcon from "@mui/icons-material/Refresh"; +import { FilesToolbarBulkMenu } from "@app/components/filesPage/FilesToolbarBulkMenu"; +import { FilesToolbarCount } from "@app/components/filesPage/FilesToolbarCount"; +import { FilesToolbarFilterMenu } from "@app/components/filesPage/FilesToolbarFilterMenu"; +import { FilesToolbarSortMenu } from "@app/components/filesPage/FilesToolbarSortMenu"; import { stripBasePath } from "@app/constants/app"; import { useAuth } from "@app/auth/UseSession"; @@ -71,6 +75,10 @@ import { FolderNameDialog } from "@app/components/filesPage/FolderNameDialog"; import { DeleteFolderDialog } from "@app/components/filesPage/DeleteFolderDialog"; import { DeleteFilesDialog } from "@app/components/filesPage/DeleteFilesDialog"; import { VersionHistoryModal } from "@app/components/filesPage/VersionHistoryModal"; +import { RenameFileDialog } from "@app/components/shared/RenameFileDialog"; +import { duplicateStoredFile } from "@app/utils/duplicateFile"; +import { downloadFileFromStorage } from "@app/utils/downloadUtils"; +import { fileStorage } from "@app/services/fileStorage"; import { materializeServerStubs } from "@app/services/fileSyncService"; import { FILES_PAGE_DRAG_TYPE, @@ -794,6 +802,92 @@ export default function FileManagerView() { [removeFiles], ); + // ─── per-file kebab: download / rename / duplicate ─────────────────────── + // Same actions the file sidebar's kebab offers, so both surfaces match. + + /** Cloud-only rows hold no bytes; pull them local before acting on them. */ + const localCopyOf = useCallback( + async (file: StirlingFileStub): Promise => { + const [materialized] = await materializeServerStubs([file], { + addFiles: fileActions.addFilesWithOptions, + updateStub: fileActions.updateStirlingFileStub, + }); + return materialized ?? null; + }, + [fileActions], + ); + + const handleDownloadFile = useCallback( + async (file: StirlingFileStub) => { + try { + const local = await localCopyOf(file); + if (!local) return; + await downloadFileFromStorage(local); + } catch (err) { + console.error("[FilesPage] Download failed", err); + folders.setError( + t("filesPage.error.downloadFailed", "Could not download the file."), + ); + } + }, + [localCopyOf, folders, t], + ); + + const handleDuplicateFile = useCallback( + async (file: StirlingFileStub) => { + try { + const local = await localCopyOf(file); + if (!local) return; + const copyId = await duplicateStoredFile( + local, + allFiles.map((f) => f.name), + addFiles, + ); + if (!copyId) { + throw new Error(`File "${local.name}" not found in storage`); + } + await refresh(); + } catch (err) { + console.error("[FilesPage] Duplicate failed", err); + folders.setError( + t("filesPage.error.duplicateFailed", "Could not duplicate the file."), + ); + } + }, + [localCopyOf, allFiles, addFiles, refresh, folders, t], + ); + + const [renameTarget, setRenameTarget] = useState( + null, + ); + + // The stub name is what the UI and exports read, so a rename is a metadata + // write; the workbench copy (if any) is updated in the same breath. + const handleConfirmRename = useCallback( + async (name: string) => { + const file = renameTarget; + if (!file) return; + const local = await localCopyOf(file); + if (!local) return; + // quickKey is name|size|lastModified; a stale one would make a re-upload + // of the original look like a duplicate of the renamed file. + const quickKey = `${name}|${local.size}|${local.lastModified}`; + const saved = await fileStorage.updateFileMetadata(local.id, { + name, + quickKey, + }); + if (!saved) { + throw new Error( + t("fileSidebar.rename.error", "Could not rename the file."), + ); + } + fileActions.updateStirlingFileStub(local.id, { name, quickKey }); + setRenameTarget(null); + await refresh(); + }, + [renameTarget, localCopyOf, fileActions, refresh, t], + ); + // ─── derived UI bits ──────────────────────────────────────────────────── const currentFolderRecord = currentFolderId ? (foldersById.get(currentFolderId) ?? null) @@ -803,6 +897,9 @@ export default function FileManagerView() { () => Array.from(selectedFileIds), [selectedFileIds], ); + // A phone with files selected shows a contextual selection bar instead of the + // full toolbar - five bulk buttons plus filters cannot fit the width. + const mobileSelection = isMobile && selectedFiles.length > 0; // Local-only subset of selection; drives Save-to-server visibility. const localOnlySelectedStubs = useMemo( @@ -1120,22 +1217,12 @@ export default function FileManagerView() { })()}
- - {loading - ? t("filesPage.loading", "Loading…") - : t("filesPage.summary", "{{count}} items", { - count: totalCount, - })} - {selectedFiles.length > 0 && ( - - {" "} - ·{" "} - {t("filesPage.selectedCount", "{{count}} selected", { - count: selectedFiles.length, - })} - - )} - + {(() => { // Select all / Clear toggle over visible files. if (visibleFiles.length === 0) return null; @@ -1175,289 +1262,382 @@ export default function FileManagerView() { ); })()}
- {selectedFiles.length > 0 && - (() => { - // Bulk-action labels; CSS collapses to icon-only below 900px. - const addLabel = + {mobileSelection ? ( + handleAddToWorkspace(selectedFiles)} + onSaveToServer={ + localOnlySelectedStubs.length > 0 + ? () => setSaveToServerTarget(localOnlySelectedStubs) + : undefined + } + saveToServerDisabledReason={ + saveToServerDisabledReason ?? undefined + } + onShowDetails={ selectedFiles.length === 1 - ? t("filesPage.addToWorkspace", "Add to workspace") - : t( - "filesPage.addToWorkspaceCount", - "Add {{count}} to workspace", - { count: selectedFiles.length }, - ); - const moveLabel = t("filesPage.moveTo", "Move to…"); - const removeLabel = t("filesPage.remove", "Remove"); - return ( - // wrap="nowrap" keeps the row single-line. - - - - - {/* Save to server; shown whenever local-only files are + ? () => setMobileDetailsOpen(true) + : undefined + } + onMove={() => promptMoveFiles(selectedFiles)} + onRemove={() => handleRemoveFiles(selectedFiles)} + /> + ) : ( + <> + {selectedFiles.length > 0 && + (() => { + // Bulk-action labels; CSS collapses to icon-only below 900px. + const addLabel = + selectedFiles.length === 1 + ? t("filesPage.addToWorkspace", "Add to workspace") + : t( + "filesPage.addToWorkspaceCount", + "Add {{count}} to workspace", + { count: selectedFiles.length }, + ); + const moveLabel = t("filesPage.moveTo", "Move to…"); + const removeLabel = t("filesPage.remove", "Remove"); + return ( + // wrap="nowrap" keeps the row single-line. + + + + + {/* Save to server; shown whenever local-only files are selected. When storage is off it stays visible but disabled, tooltip pointing at the admin. */} - {localOnlySelectedStubs.length > 0 && ( - - + + )} + {/* Show details button on compact viewports. */} + {selectedFiles.length === 1 && + isCompactDetailsViewport && ( + + + )} - > - {t("filesPage.saveToServer", "Save to server")} - - - )} - {/* Show details button on compact viewports. */} - {selectedFiles.length === 1 && - isCompactDetailsViewport && ( - + + + + + + clearSelection()} + aria-label={t( + "filesPage.clearSelection", + "Clear selection", + )} + > + × + + + + ); + })()} + {selectedFiles.length > 0 && ( +
@@ -1503,6 +1683,9 @@ export default function FileManagerView() { onPromptMoveFiles={promptMoveFiles} onSaveToServer={(file) => setSaveToServerTarget([file])} onVersionHistory={(file) => setVersionHistoryFile(file)} + onDownloadFile={handleDownloadFile} + onRenameFile={setRenameTarget} + onDuplicateFile={handleDuplicateFile} saveToServerDisabledReason={saveToServerDisabledReason} // Center-of-grid CTAs when the empty state shows - same // handlers the corner header buttons use so behaviour @@ -1662,6 +1845,14 @@ export default function FileManagerView() { onConfirm={confirmRemoveFiles} /> + {/* Rename (opened from the card kebab). */} + setRenameTarget(null)} + onSubmit={handleConfirmRename} + /> + {/* Version journey in a modal (opened from the card kebab). */} span { + white-space: nowrap; + } + .files-page-card-meta-sep { + display: none; + } +} + /* Parent-folder breadcrumb shown on cards/rows during recursive search so the user can tell which folder each hit lives in without navigating. */ .files-page-card-path { @@ -1325,33 +1355,43 @@ input { display: none; } -@media (max-width: 900px) { +@media (max-width: 1024px) { .files-page-toolbar { /* nowrap so "7 items" + "Select all" sit on the same row as the filter dropdowns and view-toggle instead of stacking on three - separate lines. Per-child min-width:0 lets them shrink as needed. - Used to only kick in at ≤640px which left a broken zone where - both side panels were hidden but the toolbar was still wrapping - to multiple rows. */ + separate lines. Runs to the app's mobile breakpoint: capping it at + 900px left 901-1024px wrapping to two rows, which is the band the + mobile layout actually renders in. + + Scrolls rather than clips. With a selection active the bulk-action + strip cannot fit any phone width, and `overflow-x: hidden` put those + buttons permanently out of reach behind the edge. */ flex-wrap: nowrap; gap: 0.35rem; padding: 0.35rem 0.5rem; min-height: auto; - overflow-x: hidden; + overflow-x: auto; + scrollbar-width: none; + } + + .files-page-toolbar::-webkit-scrollbar { + display: none; } .files-page-toolbar-info { - - /* Was `flex-basis: 100%` which forced a row break. Let it share - the row, shrink hard if needed, and ellipsize so the count line - collapses gracefully (was overlapping the bulk-action buttons - at ~400px because no truncation rule existed). */ - flex: 0 1 auto; + + /* The toolbar's only status text. Pinned, because against nowrap + siblings it lost every shrink round and rendered as "3 i". */ + flex-shrink: 0; min-width: 0; white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + } + + /* Filter and sort collapse to icon triggers here (see FilesToolbar*Menu); + they are the whole control, so they never shrink. */ + .files-page-toolbar-icon-btn { + flex-shrink: 0; } .files-page-toolbar-actions { @@ -1387,48 +1427,54 @@ input { navigation, so the in-header Home/Apps/Close trio is duplicated and the first to go. Same for "Upload" - the user can use the centre drop overlay. */ -@media (max-width: 640px) { - /* Drop the 3-column grid on phones; flex-wrap lets the search slip onto - * its own row when chrome is too cramped to share. */ +/* ── Mobile + tablet chrome (≤1024px = useIsMobile) ────────────────── + The desktop header is a 3-column grid whose middle track can grow to + 40rem. Below ~1024px that track eats the row: the breadcrumb column + collapsed to ~36px (wrapping "All files" to two lines) and the action + column overflowed, pushing Upload off the right edge. One flex row + instead - breadcrumb and actions keep their intrinsic width and the + search takes whatever is left. Ends at the app's mobile breakpoint so + it matches the layout HomePage is already rendering. */ +@media (max-width: 1024px) { .files-page-header { display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; + align-items: center; gap: 0.4rem; - padding: 0 0.4rem; + padding: 0.25rem 0.4rem; overflow-x: hidden; } - - .files-page-header [data-mobile-hide="true"] { - display: none !important; + + .files-page-header-search { + flex: 1 1 auto; + min-width: 0; + justify-content: flex-start; } - - .files-page-header [data-desktop-hide="true"] { - display: inline-flex !important; - } - - /* Mobile-hide for sub-toolbar create buttons. */ - .files-page-toolbar [data-mobile-hide="true"] { - display: none !important; + + /* Undo the fixed 24rem basis so the pill tracks the row's spare width. */ + .files-page-header-search .super-search { + flex: 1 1 auto; + width: 100%; + max-width: none; } .files-page-header-actions { + flex: 0 0 auto; margin-left: auto; - gap: 0.3rem; + gap: 0.25rem; flex-wrap: nowrap; } .files-page-breadcrumbs { + flex: 0 1 auto; font-size: 0.85rem; flex-wrap: nowrap; overflow-x: auto; min-width: 0; } - - /* Upload becomes an icon-only square button on mobile so the action - row stops getting clipped. Scoped to `.files-page-header-actions` - so the Back button at the header level keeps its visible "Back" - label (Back has no other on-screen indicator that it's about leaving). */ + + /* Icon-only actions: the labels are what pushed Upload past the edge. */ .files-page-header-actions .mantine-Button-root { padding-left: 0.55rem; padding-right: 0.55rem; @@ -1441,6 +1487,9 @@ input { .files-page-header-actions .mantine-Button-label { display: none; } +} + +@media (max-width: 640px) { /* Grid: single column on very narrow phones; two columns from ~440px */ .files-page-grid { diff --git a/frontend/editor/src/core/components/filesPage/FilesToolbarBulkMenu.tsx b/frontend/editor/src/core/components/filesPage/FilesToolbarBulkMenu.tsx new file mode 100644 index 0000000000..3e344ac7b0 --- /dev/null +++ b/frontend/editor/src/core/components/filesPage/FilesToolbarBulkMenu.tsx @@ -0,0 +1,104 @@ +import { Menu } from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import CloudUploadIcon from "@mui/icons-material/CloudUpload"; +import DeleteIcon from "@mui/icons-material/Delete"; +import DriveFileMoveIcon from "@mui/icons-material/DriveFileMove"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; + +import { Button } from "@app/ui/Button"; + +interface FilesToolbarBulkMenuProps { + selectedCount: number; + onAddToWorkspace: () => void; + /** Local-only files in the selection; omit when there are none to upload. */ + onSaveToServer?: () => void; + /** Set when storage is off - the item stays listed but disabled. */ + saveToServerDisabledReason?: string; + onShowDetails?: () => void; + onMove: () => void; + onRemove: () => void; +} + +/** + * Bulk actions behind one trigger. The full strip is five buttons wide, which + * no phone can hold alongside the count and the clear control, so rather than + * letting the row scroll them off the edge they collapse into a menu where + * every action keeps its label. + */ +export function FilesToolbarBulkMenu({ + selectedCount, + onAddToWorkspace, + onSaveToServer, + saveToServerDisabledReason, + onShowDetails, + onMove, + onRemove, +}: FilesToolbarBulkMenuProps) { + const { t } = useTranslation(); + + const addLabel = + selectedCount === 1 + ? t("filesPage.addToWorkspace", "Add to workspace") + : t("filesPage.addToWorkspaceCount", "Add {{count}} to workspace", { + count: selectedCount, + }); + + return ( + + + + + + } + onClick={onAddToWorkspace} + > + {addLabel} + + {onSaveToServer && ( + } + disabled={Boolean(saveToServerDisabledReason)} + onClick={onSaveToServer} + > + {t("filesPage.saveToServer", "Save to server")} + + )} + {onShowDetails && ( + } + onClick={onShowDetails} + > + {t("filesPage.showDetails", "Show details")} + + )} + } + onClick={onMove} + > + {t("filesPage.moveTo", "Move to…")} + + + } + onClick={onRemove} + > + {t("filesPage.remove", "Remove")} + + + + ); +} + +export default FilesToolbarBulkMenu; diff --git a/frontend/editor/src/core/components/filesPage/FilesToolbarCount.tsx b/frontend/editor/src/core/components/filesPage/FilesToolbarCount.tsx new file mode 100644 index 0000000000..d808638d63 --- /dev/null +++ b/frontend/editor/src/core/components/filesPage/FilesToolbarCount.tsx @@ -0,0 +1,41 @@ +import { useTranslation } from "react-i18next"; + +interface FilesToolbarCountProps { + loading: boolean; + totalCount: number; + selectedCount: number; + /** + * Selection-bar mode: report only the selection. A phone spends the room on + * the actions rather than on "3 items · 3 selected". + */ + selectionOnly: boolean; +} + +/** Status text at the head of the files toolbar. */ +export function FilesToolbarCount({ + loading, + totalCount, + selectedCount, + selectionOnly, +}: FilesToolbarCountProps) { + const { t } = useTranslation(); + + const selected = t("filesPage.selectedCount", "{{count}} selected", { + count: selectedCount, + }); + + if (selectionOnly) { + return {selected}; + } + + return ( + + {loading + ? t("filesPage.loading", "Loading…") + : t("filesPage.summary", "{{count}} items", { count: totalCount })} + {selectedCount > 0 && · {selected}} + + ); +} + +export default FilesToolbarCount; diff --git a/frontend/editor/src/core/components/filesPage/FilesToolbarFilterMenu.tsx b/frontend/editor/src/core/components/filesPage/FilesToolbarFilterMenu.tsx new file mode 100644 index 0000000000..81d84daf67 --- /dev/null +++ b/frontend/editor/src/core/components/filesPage/FilesToolbarFilterMenu.tsx @@ -0,0 +1,149 @@ +import { MultiSelect, Popover, Select, Stack, TextInput } from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import CloseIcon from "@mui/icons-material/Close"; +import SearchIcon from "@mui/icons-material/Search"; +import TuneIcon from "@mui/icons-material/Tune"; + +import { ActionIcon } from "@app/ui/ActionIcon"; +import { Button } from "@app/ui/Button"; +import { Tooltip } from "@app/components/shared/Tooltip"; +import type { FilesPageOriginFilter } from "@app/contexts/FilesPageContext"; + +interface FilesToolbarFilterMenuProps { + originFilter: FilesPageOriginFilter; + onOriginChange: (value: FilesPageOriginFilter) => void; + availableTypes: string[]; + typeFilter: string[]; + onTypeChange: (value: string[]) => void; + search: string; + onSearchChange: (value: string) => void; +} + +/** + * Source, type and name filters collapsed behind one icon. Side by side these + * three need ~480px, so on narrow viewports they were each truncated to + * unreadable stubs ("All sour"). In the popover they get their full width back, + * and a dot on the trigger keeps an active filter discoverable while hidden. + */ +export function FilesToolbarFilterMenu({ + originFilter, + onOriginChange, + availableTypes, + typeFilter, + onTypeChange, + search, + onSearchChange, +}: FilesToolbarFilterMenuProps) { + const { t } = useTranslation(); + + const activeCount = + (originFilter !== "all" ? 1 : 0) + + (typeFilter.length > 0 ? 1 : 0) + + (search.trim() !== "" ? 1 : 0); + const label = t("filesPage.filters.label", "Filters"); + + const clearAll = () => { + onOriginChange("all"); + onTypeChange([]); + onSearchChange(""); + }; + + return ( + + +
+ 0 + ? t( + "filesPage.filters.activeCount", + "{{count}} filters active", + { + count: activeCount, + }, + ) + : label + } + position="bottom" + > + 0 ? "primary" : "tertiary"} + size="sm" + aria-label={label} + className="files-page-toolbar-icon-btn" + > + + + +
+
+ + + onSearchChange(e.currentTarget.value)} + placeholder={t("filesPage.search.placeholder", "Filter files…")} + leftSection={} + rightSection={ + search ? ( + onSearchChange("")} + aria-label={t("filesPage.search.clear", "Clear filter")} + > + + + ) : null + } + aria-label={t("filesPage.search.label", "Filter files by name")} + /> + update({ profile: value || "ua1" })} + data={profileOptions} + disabled={disabled} + comboboxProps={{ zIndex: Z_INDEX_AUTOMATE_DROPDOWN }} + data-testid="pdfua-profile-select" + /> + + + update({ language: event.currentTarget.value })} + disabled={disabled} + data-testid="pdfua-language-input" + /> + + + update({ overrideLanguage: event.currentTarget.checked }) + } + disabled={disabled} + data-testid="pdfua-override-language" + /> + + update({ title: event.currentTarget.value })} + disabled={disabled} + data-testid="pdfua-title-input" + /> + + + update({ embedFonts: event.currentTarget.checked }) + } + disabled={disabled} + data-testid="pdfua-embed-fonts" + /> + + + + {t( + "convert.pdfUaAltTextNotice", + "Images need a written description before a document can be certified. Descriptions are never generated automatically, because an invented one passes the checker while telling a screen-reader user nothing. Any image left without one is reported, and the file comes back tagged but not certified.", + )} + + + + {tooManyFiles && ( + + + {t( + "convert.pdfUaAltTextSingleFileOnly", + "Descriptions belong to one document: an image is identified by its position, which is a different image in every file. Convert these {{fileCount}} files to tag them, then convert one at a time to describe its images.", + { fileCount: selectedFiles.length }, + )} + + + )} + + {!tooManyFiles && ( + + )} + + {scanError && ( + + {scanError} + + )} + + {!tooManyFiles && figures?.length === 0 && ( + + {t( + "convert.pdfUaNoImagesNeedingText", + "No image is missing a description.", + )} + + )} + + {!tooManyFiles && + figures?.map((figure) => ( + + update({ + altText: formatAltText({ + ...descriptions, + [figure.key]: event.currentTarget.value, + }), + }) + } + disabled={disabled} + data-testid={`pdfua-alt-text-${figure.key}`} + /> + ))} + + ); +}; + +export default ConvertToPdfUaSettings; diff --git a/frontend/editor/src/core/components/tools/convert/ConvertToPdfaSettings.tsx b/frontend/editor/src/core/components/tools/convert/ConvertToPdfaSettings.tsx index 298fcc847b..fa035eb067 100644 --- a/frontend/editor/src/core/components/tools/convert/ConvertToPdfaSettings.tsx +++ b/frontend/editor/src/core/components/tools/convert/ConvertToPdfaSettings.tsx @@ -3,6 +3,7 @@ import { Stack, Text, Select, Alert, Checkbox } from "@mantine/core"; import { useTranslation } from "react-i18next"; import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters"; import { usePdfSignatureDetection } from "@app/hooks/usePdfSignatureDetection"; +import { useEndpointEnabled } from "@app/hooks/useEndpointConfig"; import { StirlingFile } from "@app/types/fileContext"; import { Z_INDEX_AUTOMATE_DROPDOWN } from "@app/styles/zIndex"; @@ -26,11 +27,21 @@ const ConvertToPdfaSettings = ({ const { hasDigitalSignatures, isChecking } = usePdfSignatureDetection(selectedFiles); const outputFormatLabelId = useId(); + // Level A needs the same tagger as PDF/UA, so it stands or falls with that endpoint. + const { enabled: taggingAvailable } = useEndpointEnabled("pdf-to-ua"); const pdfaFormatOptions = [ { value: "pdfa-1", label: "PDF/A-1b" }, { value: "pdfa-2b", label: "PDF/A-2b" }, { value: "pdfa-3b", label: "PDF/A-3b" }, + // Level A is level B plus accessibility: it additionally tags the document. + ...(taggingAvailable === false + ? [] + : [ + { value: "pdfa-1a", label: "PDF/A-1a (accessible)" }, + { value: "pdfa-2a", label: "PDF/A-2a (accessible)" }, + { value: "pdfa-3a", label: "PDF/A-3a (accessible)" }, + ]), ]; return ( diff --git a/frontend/editor/src/core/components/viewer/NonPdfViewer.tsx b/frontend/editor/src/core/components/viewer/NonPdfViewer.tsx index bd2704963a..47d442ef3d 100644 --- a/frontend/editor/src/core/components/viewer/NonPdfViewer.tsx +++ b/frontend/editor/src/core/components/viewer/NonPdfViewer.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from "react"; +import React, { useCallback, useMemo } from "react"; import { Box, Center, Stack, Text } from "@mantine/core"; import { Button } from "@app/ui/Button"; import ArticleIcon from "@mui/icons-material/Article"; @@ -99,12 +99,17 @@ export function NonPdfViewer({ file }: NonPdfViewerProps) { return ( (null); + // Phones render a desktop-width document into ~400px, which reads as a blank + // column, so the iframe is opt-in there. Derived rather than seeded into + // state because useIsMobile resolves after first paint. + const [optedIn, setOptedIn] = useState(false); + const showPreview = !isMobile || optedIn; useEffect(() => { + if (!showPreview) return; const url = URL.createObjectURL(file); setObjectUrl(url); return () => URL.revokeObjectURL(url); - }, [file]); + }, [file, showPreview]); return ( - - {t("viewer.nonPdf.htmlPreviewWarning", { - size: formatFileSize(file.size), - })} - + + + {t("viewer.nonPdf.htmlPreviewWarning", { + size: formatFileSize(file.size), + })} + + {/* Opting in used to be one-way: the only way back was closing and + reopening the file. */} + {isMobile && optedIn && ( + + )} + - {objectUrl && ( -