mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-03 14:50:31 +00:00
Compare commits
40
Commits
stumpy/ideas
...
l10n_dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7f7987906 | ||
|
|
6ce43be400 | ||
|
|
0204009b66 | ||
|
|
cec0b99d2e | ||
|
|
3615a5a2a5 | ||
|
|
56687614df | ||
|
|
f1ae9c00ac | ||
|
|
bc2ff953ed | ||
|
|
e17689a91b | ||
|
|
764d1a492d | ||
|
|
9d50a3b261 | ||
|
|
da3de299fc | ||
|
|
3a022e2dc4 | ||
|
|
58bda48682 | ||
|
|
b2a2e15ead | ||
|
|
e865185425 | ||
|
|
d02c19a09e | ||
|
|
4a106454ce | ||
|
|
4254100fae | ||
|
|
74f727b1f3 | ||
|
|
fb6fad952e | ||
|
|
f9b4e0c8e0 | ||
|
|
e5041097af | ||
|
|
61f36eb253 | ||
|
|
e50bcc2efb | ||
|
|
88000aab45 | ||
|
|
01b4ab392c | ||
|
|
52aa8758e3 | ||
|
|
4b7cc1c0c3 | ||
|
|
f02d7455be | ||
|
|
a61c35fcd8 | ||
|
|
64552d5d5b | ||
|
|
a59edf3e5b | ||
|
|
6a5d06ee26 | ||
|
|
126ec414a8 | ||
|
|
773744d04b | ||
|
|
7b98085b31 | ||
|
|
b3bf220f56 | ||
|
|
d4429c3cc7 | ||
|
|
4f777d7438 |
@@ -0,0 +1,53 @@
|
||||
name: Install apt packages
|
||||
description: >
|
||||
Installs apt packages with a per-attempt timeout and retries. The hosted runner's Azure apt mirror intermittently serves archives at a crawl, which apt's own timeout does not catch. A stuck attempt is killed and retried, which usually lands on a healthy mirror node.
|
||||
|
||||
inputs:
|
||||
packages:
|
||||
description: Space separated list of packages to install
|
||||
required: true
|
||||
attempts:
|
||||
description: Maximum number of attempts
|
||||
default: "3"
|
||||
attempt-timeout:
|
||||
description: Seconds before a single attempt is killed
|
||||
default: "180"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install ${{ inputs.packages }}
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGES: ${{ inputs.packages }}
|
||||
ATTEMPTS: ${{ inputs.attempts }}
|
||||
ATTEMPT_TIMEOUT: ${{ inputs.attempt-timeout }}
|
||||
run: |
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
SUDO="sudo"
|
||||
fi
|
||||
|
||||
attempt_install() {
|
||||
$SUDO apt-get update -q
|
||||
# Intentionally unquoted, this is a list of package names
|
||||
$SUDO apt-get install -q --yes --no-install-recommends $PACKAGES
|
||||
}
|
||||
export -f attempt_install
|
||||
export SUDO PACKAGES
|
||||
|
||||
for attempt in $(seq 1 "${ATTEMPTS}"); do
|
||||
echo "::group::apt install attempt ${attempt}/${ATTEMPTS}"
|
||||
start=$(date +%s)
|
||||
if timeout --kill-after=10 "${ATTEMPT_TIMEOUT}" bash -c attempt_install; then
|
||||
echo "::endgroup::"
|
||||
echo "Installed in $(( $(date +%s) - start ))s"
|
||||
exit 0
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
echo "::warning::apt install attempt ${attempt}/${ATTEMPTS} failed or exceeded ${ATTEMPT_TIMEOUT}s"
|
||||
# A killed attempt can leave dpkg half configured
|
||||
$SUDO dpkg --configure -a || true
|
||||
done
|
||||
|
||||
echo "::error::apt install failed after ${ATTEMPTS} attempts"
|
||||
exit 1
|
||||
@@ -59,7 +59,6 @@ updates:
|
||||
- "drf-*"
|
||||
- "djangorestframework"
|
||||
- "whitenoise"
|
||||
- "bleach"
|
||||
- "jinja2"
|
||||
# Async, Task Queuing & Caching
|
||||
async-tasks:
|
||||
|
||||
@@ -102,17 +102,16 @@ jobs:
|
||||
with:
|
||||
python-version: "${{ matrix.python-version }}"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: true
|
||||
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||
- name: Install system dependencies
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends \
|
||||
unpaper tesseract-ocr imagemagick ghostscript poppler-utils
|
||||
timeout-minutes: 12
|
||||
uses: $/.github/actions/apt-install
|
||||
with:
|
||||
packages: unpaper tesseract-ocr imagemagick ghostscript poppler-utils
|
||||
- name: Configure ImageMagick
|
||||
run: |
|
||||
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml
|
||||
@@ -139,13 +138,13 @@ jobs:
|
||||
pytest
|
||||
- name: Upload test results to Codecov
|
||||
if: always()
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
with:
|
||||
flags: backend-python-${{ matrix.python-version }}
|
||||
files: junit.xml
|
||||
report_type: test_results
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
with:
|
||||
flags: backend-python-${{ matrix.python-version }}
|
||||
files: coverage.xml
|
||||
@@ -176,7 +175,7 @@ jobs:
|
||||
with:
|
||||
python-version: "${{ env.DEFAULT_PYTHON }}"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: true
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
echo "repository=${repo_name}"
|
||||
echo "name=${repo_name}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
echo "Downloaded digests:"
|
||||
ls -la /tmp/digests/
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: true
|
||||
|
||||
@@ -174,13 +174,13 @@ jobs:
|
||||
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||
- name: Upload test results to Codecov
|
||||
if: always()
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
with:
|
||||
flags: frontend-node-${{ matrix.node-version }}
|
||||
directory: src-ui/
|
||||
report_type: test_results
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
with:
|
||||
flags: frontend-node-${{ matrix.node-version }}
|
||||
directory: src-ui/coverage/
|
||||
@@ -216,7 +216,7 @@ jobs:
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
version: '0.12.x'
|
||||
enable-cache: false
|
||||
@@ -231,9 +231,10 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: cd src-ui && pnpm install --frozen-lockfile
|
||||
- name: Install backend system dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install --yes --quiet --no-install-recommends libmagic1
|
||||
timeout-minutes: 12
|
||||
uses: $/.github/actions/apt-install
|
||||
with:
|
||||
packages: libmagic1
|
||||
- name: Install backend dependencies
|
||||
env:
|
||||
PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }}
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: false
|
||||
@@ -70,9 +70,10 @@ jobs:
|
||||
run: |
|
||||
uv sync --python "${PYTHON_VERSION}" --dev --frozen
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends gettext liblept5
|
||||
timeout-minutes: 12
|
||||
uses: $/.github/actions/apt-install
|
||||
with:
|
||||
packages: gettext liblept5
|
||||
# ---- Build Documentation ----
|
||||
- name: Build documentation
|
||||
env:
|
||||
@@ -212,7 +213,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: false
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
- name: Run Semgrep
|
||||
run: semgrep scan --config auto --sarif-output results.sarif
|
||||
- name: Upload results to GitHub code scanning
|
||||
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
steps:
|
||||
- name: Clean temporary images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@4fe057d991d63b8f6d5d22c40f17c1bca2226537 # v0.12.0
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@21f875bab2376314e0525c614e923c669434f0e4 # v0.13.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "${{ github.repository_owner }}"
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
steps:
|
||||
- name: Clean untagged images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/untagged@4fe057d991d63b8f6d5d22c40f17c1bca2226537 # v0.12.0
|
||||
uses: stumpylog/image-cleaner-action/untagged@21f875bab2376314e0525c614e923c669434f0e4 # v0.13.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "${{ github.repository_owner }}"
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -47,4 +47,4 @@ jobs:
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
token: ${{ secrets.PNGX_BOT_PAT }}
|
||||
persist-credentials: false
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@0d5670f539973aea2f01abce61a8989934df0025 # v3.0.2
|
||||
uses: crowdin/github-action@9c23991700c0ec5256fd41089b9d9d7d540e424e # v3.3.0
|
||||
with:
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
|
||||
@@ -25,11 +25,12 @@ jobs:
|
||||
id: setup-python
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends gettext
|
||||
timeout-minutes: 12
|
||||
uses: $/.github/actions/apt-install
|
||||
with:
|
||||
packages: gettext
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: true
|
||||
|
||||
+706
-456
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@ repos:
|
||||
- json
|
||||
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 'v3.9.6'
|
||||
rev: 'v3.9.8'
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or:
|
||||
@@ -46,11 +46,11 @@ repos:
|
||||
- ts
|
||||
- markdown
|
||||
additional_dependencies:
|
||||
- prettier@3.9.6
|
||||
- prettier@3.9.8
|
||||
- 'prettier-plugin-organize-imports@4.3.0'
|
||||
# Python hooks
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.16.7
|
||||
rev: v0.16.8
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
- id: ruff-format
|
||||
|
||||
+4804
-12941
File diff suppressed because one or more lines are too long
+4
-2
@@ -30,7 +30,7 @@ RUN set -eux \
|
||||
# Purpose: Installs s6-overlay and rootfs
|
||||
# Comments:
|
||||
# - Don't leave anything extra in here either
|
||||
FROM ghcr.io/astral-sh/uv:0.12.16-python3.14-trixie-slim AS s6-overlay-base
|
||||
FROM ghcr.io/astral-sh/uv:0.12.20-python3.14-trixie-slim AS s6-overlay-base
|
||||
|
||||
WORKDIR /usr/src/s6
|
||||
|
||||
@@ -171,7 +171,9 @@ RUN set -eux \
|
||||
&& cp /etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml \
|
||||
&& echo "Cleaning up image layer" \
|
||||
&& rm --force --verbose *.deb \
|
||||
&& rm --recursive --force --verbose /var/lib/apt/lists/*
|
||||
&& rm --recursive --force --verbose /var/lib/apt/lists/* \
|
||||
&& echo "Configuring interactive shells to source the s6 container environment" \
|
||||
&& echo '. /etc/profile.d/contenv.sh' >> /etc/bash.bashrc
|
||||
|
||||
WORKDIR /usr/src/paperless/src/
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
greenmail:
|
||||
image: docker.io/greenmail/standalone:2.1.13
|
||||
image: docker.io/greenmail/standalone:2.1.14
|
||||
hostname: greenmail
|
||||
container_name: greenmail
|
||||
environment:
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# Source s6 container environment for interactive shells.
|
||||
# Ensures variables resolved from *_FILE secret injection are visible
|
||||
# when using 'docker exec bash'. Does not affect s6 services (those
|
||||
# use with-contenv directly). Has no effect in non-container contexts
|
||||
# because the directory will not exist.
|
||||
# Note: sh/dash shells opened via 'docker exec sh' are not covered;
|
||||
# only bash-based sessions benefit from this file.
|
||||
_pngx_contenv="/run/s6/container_environment"
|
||||
if [ -d "${_pngx_contenv}" ]; then
|
||||
for _pngx_f in "${_pngx_contenv}"/*; do
|
||||
[ -f "${_pngx_f}" ] || continue
|
||||
_pngx_name=$(basename "${_pngx_f}")
|
||||
_pngx_val=$(cat "${_pngx_f}")
|
||||
export "${_pngx_name}=${_pngx_val}"
|
||||
done
|
||||
fi
|
||||
unset _pngx_contenv _pngx_f _pngx_name _pngx_val
|
||||
@@ -136,13 +136,15 @@ for suggested generation and embedding models.
|
||||
### AI-assisted suggestions
|
||||
|
||||
With AI enabled, Paperless-ngx can suggest a title, tags, correspondent, document type,
|
||||
storage path and dates by sending the document to the LLM. This is **opt-in per request**
|
||||
and surfaces through the "Suggest" control on the document detail page, alongside the
|
||||
classic classifier-based suggestions — it does not disable them. Suggestions are requested
|
||||
automatically when you open a document that carries an inbox tag unless "Automatically request
|
||||
suggestions for inbox documents" under Settings > Documents is disabled. Suggestion output
|
||||
language can be steered with
|
||||
[`PAPERLESS_AI_LLM_OUTPUT_LANGUAGE`](configuration.md#PAPERLESS_AI_LLM_OUTPUT_LANGUAGE)
|
||||
storage path and dates by sending the document to the LLM using "Suggest" button on the document
|
||||
detail page. You can choose which type of suggestions are requested by default under Settings >
|
||||
Documents, either ML (classifier-based) suggestions, AI suggestions, or both. When both are requested
|
||||
the results are combined.
|
||||
|
||||
Suggestions are requested automatically when you open a document that carries an inbox tag
|
||||
unless "Automatically request suggestions for inbox documents" under Settings > Documents is disabled.
|
||||
|
||||
Suggestion output language can be steered with [`PAPERLESS_AI_LLM_OUTPUT_LANGUAGE`](configuration.md#PAPERLESS_AI_LLM_OUTPUT_LANGUAGE)
|
||||
(otherwise it follows the user's UI language).
|
||||
|
||||
### The LLM index (RAG) and similar documents
|
||||
|
||||
+7
-7
@@ -19,7 +19,6 @@ classifiers = [
|
||||
dependencies = [
|
||||
"azure-ai-documentintelligence>=1.0.2",
|
||||
"babel>=2.17",
|
||||
"bleach~=6.4.0",
|
||||
"celery[redis]~=5.6.2",
|
||||
"channels~=4.2",
|
||||
"channels-redis~=4.2",
|
||||
@@ -35,7 +34,7 @@ dependencies = [
|
||||
"django-cors-headers~=4.9.0",
|
||||
"django-extensions~=4.1",
|
||||
"django-filter~=25.1",
|
||||
"django-guardian>=3.3.3,<3.5",
|
||||
"django-guardian>=3.3.3,<3.6",
|
||||
"django-multiselectfield~=1.0.1",
|
||||
"django-rich~=2.2.0",
|
||||
"django-soft-delete~=1.0.18",
|
||||
@@ -45,7 +44,7 @@ dependencies = [
|
||||
"drf-spectacular-sidecar>=2026.7.1,<2026.10",
|
||||
"drf-writable-nested~=0.7.1",
|
||||
"filelock~=3.32.0",
|
||||
"flower>=2.0.1,<2.2",
|
||||
"flower>=2.0.1,<2.3",
|
||||
"gotenberg-client[httpx]~=1.0",
|
||||
"httpx-oauth~=0.17",
|
||||
"ijson>=3.5.1",
|
||||
@@ -77,6 +76,7 @@ dependencies = [
|
||||
"tantivy~=0.26.0",
|
||||
"tika-client[httpx]~=1.0",
|
||||
"torch>=2.13,<2.15",
|
||||
"turbohtml~=1.10.0",
|
||||
"watchfiles>=1.2",
|
||||
"whitenoise~=6.11",
|
||||
"whoosh-compat[tantivy]==0.3",
|
||||
@@ -84,13 +84,13 @@ dependencies = [
|
||||
]
|
||||
[project.optional-dependencies]
|
||||
mariadb = [
|
||||
"mysqlclient~=2.2.7",
|
||||
"mysqlclient>=2.2.7,<2.4",
|
||||
]
|
||||
postgres = [
|
||||
"psycopg[c,pool]==3.3.4",
|
||||
# Direct dependency for proper resolution of the pre-built wheels
|
||||
"psycopg-c==3.3.4",
|
||||
"psycopg-pool==3.3.1",
|
||||
"psycopg-pool==3.3.2",
|
||||
]
|
||||
webserver = [
|
||||
"granian[uvloop]>=2.7,<2.9",
|
||||
@@ -112,7 +112,7 @@ lint = [
|
||||
testing = [
|
||||
"daphne",
|
||||
"factory-boy~=3.3.1",
|
||||
"faker>=40.36,<40.39",
|
||||
"faker>=40.36,<40.40",
|
||||
"imagehash",
|
||||
"pytest~=9.1.1",
|
||||
"pytest-cov~=7.1.0",
|
||||
@@ -136,7 +136,6 @@ typing = [
|
||||
"mypy",
|
||||
"mypy-baseline",
|
||||
"pyrefly",
|
||||
"types-bleach",
|
||||
"types-channels",
|
||||
"types-colorama",
|
||||
"types-dateparser",
|
||||
@@ -151,6 +150,7 @@ typing = [
|
||||
|
||||
[tool.uv]
|
||||
required-version = ">=0.9.0"
|
||||
prerelease = "disallow"
|
||||
environments = [
|
||||
"sys_platform == 'darwin'",
|
||||
"sys_platform == 'linux'",
|
||||
|
||||
@@ -91,6 +91,11 @@
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/pdfjs-dist/iccs/",
|
||||
"output": "/assets/iccs/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/pdfjs-dist/cmaps/",
|
||||
"output": "/assets/cmaps/"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
|
||||
+280
-217
File diff suppressed because it is too large
Load Diff
+22
-22
@@ -15,16 +15,16 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^22.1.6",
|
||||
"@angular/common": "~22.1.6",
|
||||
"@angular/compiler": "~22.1.6",
|
||||
"@angular/core": "~22.1.6",
|
||||
"@angular/forms": "~22.1.6",
|
||||
"@angular/localize": "~22.1.6",
|
||||
"@angular/platform-browser": "~22.1.6",
|
||||
"@angular/router": "~22.1.6",
|
||||
"@angular/cdk": "^22.2.0",
|
||||
"@angular/common": "~22.2.0",
|
||||
"@angular/compiler": "~22.2.0",
|
||||
"@angular/core": "~22.2.0",
|
||||
"@angular/forms": "~22.2.0",
|
||||
"@angular/localize": "~22.2.0",
|
||||
"@angular/platform-browser": "~22.2.0",
|
||||
"@angular/router": "~22.2.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^21.0.0",
|
||||
"@ng-select/ng-select": "~24.1.1",
|
||||
"@ng-select/ng-select": "~24.1.2",
|
||||
"@ngneat/dirty-check-forms": "^3.0.3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.8",
|
||||
@@ -45,29 +45,29 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/jest": "^22.0.1",
|
||||
"@angular-devkit/core": "^22.1.8",
|
||||
"@angular-devkit/schematics": "^22.1.8",
|
||||
"@angular-devkit/core": "^22.2.0",
|
||||
"@angular-devkit/schematics": "^22.2.0",
|
||||
"@angular-eslint/builder": "22.5.0",
|
||||
"@angular-eslint/eslint-plugin": "22.5.0",
|
||||
"@angular-eslint/eslint-plugin-template": "22.5.0",
|
||||
"@angular-eslint/schematics": "22.5.0",
|
||||
"@angular-eslint/template-parser": "22.5.0",
|
||||
"@angular/build": "22.1.8",
|
||||
"@angular/cli": "22.1.8",
|
||||
"@angular/compiler-cli": "~22.1.6",
|
||||
"@angular/build": "22.2.0",
|
||||
"@angular/cli": "22.2.0",
|
||||
"@angular/compiler-cli": "~22.2.0",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^26.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.70.0",
|
||||
"@typescript-eslint/parser": "^8.70.0",
|
||||
"@typescript-eslint/utils": "^8.70.0",
|
||||
"eslint": "^10.10.0",
|
||||
"jest": "30.5.1",
|
||||
"jest-environment-jsdom": "^30.5.1",
|
||||
"@types/node": "^26.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.70.1",
|
||||
"@typescript-eslint/parser": "^8.70.1",
|
||||
"@typescript-eslint/utils": "^8.70.1",
|
||||
"eslint": "^10.11.0",
|
||||
"jest": "30.5.2",
|
||||
"jest-environment-jsdom": "^30.5.2",
|
||||
"jest-junit": "^17.0.0",
|
||||
"jest-preset-angular": "^17.0.0",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"prettier": "^3.9.6",
|
||||
"prettier": "^3.9.9",
|
||||
"prettier-plugin-organize-imports": "^4.3.0",
|
||||
"ts-node": "~10.9.2",
|
||||
"typescript": "^6.0.3"
|
||||
|
||||
Generated
+1312
-1960
File diff suppressed because it is too large
Load Diff
@@ -253,6 +253,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (aiEnabled) {
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Suggestions default to</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<fieldset class="btn-group btn-group-sm">
|
||||
<input type="radio" class="btn-check" id="suggestionSourceBoth" [value]="SuggestionSource.Both" formControlName="documentEditingSuggestionSource">
|
||||
<label class="btn btn-outline-primary" for="suggestionSourceBoth"><ng-container i18n>Both</ng-container></label>
|
||||
<input type="radio" class="btn-check" id="suggestionSourceML" [value]="SuggestionSource.ML" formControlName="documentEditingSuggestionSource">
|
||||
<label class="btn btn-outline-primary" for="suggestionSourceML"><i-bs class="me-1" name="cpu"></i-bs><ng-container i18n>ML only</ng-container></label>
|
||||
<input type="radio" class="btn-check" id="suggestionSourceAI" [value]="SuggestionSource.AI" formControlName="documentEditingSuggestionSource">
|
||||
<label class="btn btn-outline-primary" for="suggestionSourceAI"><i-bs class="me-1" name="stars"></i-bs><ng-container i18n>AI only</ng-container></label>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<pngx-input-check i18n-title title="Automatically request suggestions for inbox documents" i18n-hint hint="If un-checked, suggestions must be requested via the Suggest button." formControlName="documentEditingAutoSuggest"></pngx-input-check>
|
||||
|
||||
@@ -307,7 +307,7 @@ describe('SettingsComponent', () => {
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(storeSpy).toHaveBeenCalled()
|
||||
expect(appearanceSettingsSpy).not.toHaveBeenCalled()
|
||||
expect(setSpy).toHaveBeenCalledTimes(34)
|
||||
expect(setSpy).toHaveBeenCalledTimes(35)
|
||||
expect(setSpy).toHaveBeenCalledWith(SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS, [
|
||||
HideableSidebarItemID.Workflows,
|
||||
])
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
HIDEABLE_SIDEBAR_ITEM_IDS,
|
||||
HideableSidebarItemID,
|
||||
SETTINGS_KEYS,
|
||||
SuggestionSource,
|
||||
} from 'src/app/data/ui-settings'
|
||||
import { User } from 'src/app/data/user'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
@@ -184,6 +185,7 @@ export class SettingsComponent
|
||||
documentEditingRemoveInboxTags: new FormControl(null),
|
||||
documentEditingOverlayThumbnail: new FormControl(null),
|
||||
documentEditingAutoSuggest: new FormControl(null),
|
||||
documentEditingSuggestionSource: new FormControl(null),
|
||||
documentDetailsHiddenFields: new FormControl([]),
|
||||
searchDbOnly: new FormControl(null),
|
||||
searchLink: new FormControl(null),
|
||||
@@ -217,6 +219,11 @@ export class SettingsComponent
|
||||
public readonly PdfZoomScale = PdfZoomScale
|
||||
|
||||
public readonly PdfEditorEditMode = PdfEditorEditMode
|
||||
public readonly SuggestionSource = SuggestionSource
|
||||
|
||||
get aiEnabled(): boolean {
|
||||
return this.settings.get(SETTINGS_KEYS.AI_ENABLED)
|
||||
}
|
||||
|
||||
public readonly documentDetailFieldOptions = documentDetailFieldOptions
|
||||
public readonly sidebarItemOptions = HIDEABLE_SIDEBAR_ITEM_IDS.map((id) => ({
|
||||
@@ -404,6 +411,9 @@ export class SettingsComponent
|
||||
documentEditingAutoSuggest: this.settings.get(
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_AUTO_SUGGEST
|
||||
),
|
||||
documentEditingSuggestionSource: this.settings.get(
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE
|
||||
),
|
||||
documentDetailsHiddenFields: this.settings.get(
|
||||
SETTINGS_KEYS.DOCUMENT_DETAILS_HIDDEN_FIELDS
|
||||
),
|
||||
@@ -625,6 +635,10 @@ export class SettingsComponent
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_AUTO_SUGGEST,
|
||||
this.settingsForm.value.documentEditingAutoSuggest
|
||||
)
|
||||
this.settings.set(
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
|
||||
this.settingsForm.value.documentEditingSuggestionSource
|
||||
)
|
||||
this.settings.set(
|
||||
SETTINGS_KEYS.DOCUMENT_DETAILS_HIDDEN_FIELDS,
|
||||
this.settingsForm.value.documentDetailsHiddenFields
|
||||
|
||||
@@ -154,11 +154,29 @@
|
||||
& section {
|
||||
position: absolute;
|
||||
text-align: initial;
|
||||
pointer-events: auto;
|
||||
box-sizing: border-box;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
& :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
font-size: 1em;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
& :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder)
|
||||
> a:hover {
|
||||
opacity: 0.2;
|
||||
background-color: rgb(255 255 0);
|
||||
}
|
||||
|
||||
& .annotationTextContent {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:host ::ng-deep .textLayer.selecting ~ .annotationLayer section {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { SimpleChange } from '@angular/core'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import * as pdfjs from 'pdfjs-dist/legacy/build/pdf.mjs'
|
||||
import { PDFSinglePageViewer, PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs'
|
||||
import {
|
||||
LinkTarget,
|
||||
PDFSinglePageViewer,
|
||||
PDFViewer,
|
||||
} from 'pdfjs-dist/web/pdf_viewer.mjs'
|
||||
import { PngxPdfViewerComponent } from './pdf-viewer.component'
|
||||
import { PdfRenderMode, PdfZoomLevel, PdfZoomScale } from './pdf-viewer.types'
|
||||
|
||||
@@ -58,6 +62,16 @@ describe('PngxPdfViewerComponent', () => {
|
||||
expect((component as any).pdfViewer).toBeInstanceOf(PDFViewer)
|
||||
})
|
||||
|
||||
it('opens external links in a new tab', () => {
|
||||
const linkService = (component as any).linkService
|
||||
expect(linkService.options).toEqual(
|
||||
expect.objectContaining({
|
||||
externalLinkTarget: LinkTarget.BLANK,
|
||||
externalLinkRel: 'noopener noreferrer nofollow',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves the worker source relative to the document base URI', async () => {
|
||||
setBaseHref('/paperless/')
|
||||
const getDocumentSpy = jest.spyOn(pdfjs, 'getDocument')
|
||||
@@ -76,6 +90,7 @@ describe('PngxPdfViewerComponent', () => {
|
||||
withCredentials: true,
|
||||
wasmUrl: expect.stringContaining('/paperless/assets/wasm/'),
|
||||
iccUrl: expect.stringContaining('/paperless/assets/iccs/'),
|
||||
cMapUrl: expect.stringContaining('/paperless/assets/cmaps/'),
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from 'pdfjs-dist/legacy/build/pdf.mjs'
|
||||
import {
|
||||
EventBus,
|
||||
LinkTarget,
|
||||
PDFFindController,
|
||||
PDFLinkService,
|
||||
PDFSinglePageViewer,
|
||||
@@ -75,7 +76,11 @@ export class PngxPdfViewerComponent
|
||||
private lastViewerPage?: number
|
||||
|
||||
private readonly eventBus = new EventBus()
|
||||
private readonly linkService = new PDFLinkService({ eventBus: this.eventBus })
|
||||
private readonly linkService = new PDFLinkService({
|
||||
eventBus: this.eventBus,
|
||||
externalLinkTarget: LinkTarget.BLANK,
|
||||
externalLinkRel: 'noopener noreferrer nofollow',
|
||||
})
|
||||
private readonly findController = new PDFFindController({
|
||||
eventBus: this.eventBus,
|
||||
linkService: this.linkService,
|
||||
@@ -185,6 +190,7 @@ export class PngxPdfViewerComponent
|
||||
withCredentials: true,
|
||||
wasmUrl: new URL('assets/wasm/', this.document.baseURI).toString(),
|
||||
iccUrl: new URL('assets/iccs/', this.document.baseURI).toString(),
|
||||
cMapUrl: new URL('assets/cmaps/', this.document.baseURI).toString(),
|
||||
}
|
||||
this.loadingTask = getDocument(initOptions)
|
||||
try {
|
||||
|
||||
+77
-51
@@ -1,58 +1,84 @@
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading() || (suggestions() && !aiEnabled())" [aria-label]="noSuggestions ? 'No suggestions' : 'Suggest'" i18n-aria-label>
|
||||
@if (loading()) {
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
} @else if (noSuggestions) {
|
||||
<i-bs width="1.2em" height="1.2em" name="check-circle"></i-bs>
|
||||
} @else {
|
||||
<i-bs width="1.2em" height="1.2em" name="stars"></i-bs>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading() || (suggestions() && !aiEnabled())" [aria-label]="noSuggestions ? 'No suggestions' : 'Suggest'" i18n-aria-label>
|
||||
@if (loading()) {
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
} @else if (noSuggestions) {
|
||||
<i-bs width="1.2em" height="1.2em" name="check-circle"></i-bs>
|
||||
} @else {
|
||||
<i-bs width="1.2em" height="1.2em" name="lightbulb"></i-bs>
|
||||
}
|
||||
@if (noSuggestions) {
|
||||
<span class="d-none d-lg-inline ps-1" i18n>No suggestions</span>
|
||||
} @else {
|
||||
<span class="d-none d-lg-inline ps-1" i18n>Suggest</span>
|
||||
}
|
||||
@if (totalSuggestions > 0) {
|
||||
<span class="badge bg-primary ms-2">{{ totalSuggestions }}</span>
|
||||
}
|
||||
</button>
|
||||
|
||||
@if (aiEnabled()) {
|
||||
<div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
|
||||
<span class="visually-hidden" i18n>Show suggestions</span>
|
||||
</button>
|
||||
|
||||
<div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown">
|
||||
<div class="list-group list-group-flush small pb-0">
|
||||
@if (novelSuggestions === 0 && reusableSuggestions === 0) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>No novel suggestions</small>
|
||||
</div>
|
||||
}
|
||||
@if (suggestions()?.suggested_tags?.length > 0) {
|
||||
<small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
|
||||
@for (tag of suggestions().suggested_tags; track tag) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
|
||||
}
|
||||
}
|
||||
@if (suggestions()?.suggested_document_types?.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div>
|
||||
@for (type of suggestions().suggested_document_types; track type) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button>
|
||||
}
|
||||
}
|
||||
@if (suggestions()?.suggested_correspondents?.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div>
|
||||
@for (correspondent of suggestions().suggested_correspondents; track correspondent) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button>
|
||||
}
|
||||
}
|
||||
@if (reusableSuggestions > 0) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>{reusableSuggestions, plural, =1 {1 existing value suggested below} other {{{reusableSuggestions}} existing values suggested below}}</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (noSuggestions) {
|
||||
<span class="d-none d-lg-inline ps-1" i18n>No suggestions</span>
|
||||
} @else {
|
||||
<span class="d-none d-lg-inline ps-1" i18n>Suggest</span>
|
||||
}
|
||||
@if (totalSuggestions > 0) {
|
||||
<span class="badge bg-primary ms-2">{{ totalSuggestions }}</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (aiEnabled()) {
|
||||
<div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
|
||||
<span class="visually-hidden" i18n>Show suggestions</span>
|
||||
<div ngbDropdown autoClose="outside" placement="bottom-end" [popperOptions]="popperOptions">
|
||||
<button type="button" class="btn btn-sm btn-link position-relative" ngbDropdownToggle [disabled]="disabled() || loading()" i18n-title title="Suggestion options">
|
||||
<i-bs name="three-dots"></i-bs>
|
||||
@if (source() !== defaultSource()) {
|
||||
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-primary border border-light rounded-circle">
|
||||
<span class="visually-hidden" i18n>Not using default</span>
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
|
||||
<div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown">
|
||||
<div class="list-group list-group-flush small pb-0">
|
||||
@if (novelSuggestions === 0 && reusableSuggestions === 0) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>No novel suggestions</small>
|
||||
</div>
|
||||
}
|
||||
@if (suggestions()?.suggested_tags?.length > 0) {
|
||||
<small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
|
||||
@for (tag of suggestions().suggested_tags; track tag) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
|
||||
}
|
||||
}
|
||||
@if (suggestions()?.suggested_document_types?.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div>
|
||||
@for (type of suggestions().suggested_document_types; track type) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button>
|
||||
}
|
||||
}
|
||||
@if (suggestions()?.suggested_correspondents?.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div>
|
||||
@for (correspondent of suggestions().suggested_correspondents; track correspondent) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button>
|
||||
}
|
||||
}
|
||||
@if (reusableSuggestions > 0) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>{reusableSuggestions, plural, =1 {1 existing value suggested below} other {{{reusableSuggestions}} existing values suggested below}}</small>
|
||||
</div>
|
||||
}
|
||||
<div ngbDropdownMenu class="shadow p-3">
|
||||
<div class="small text-muted mb-2" i18n>Suggest using:</div>
|
||||
<div class="form-check small">
|
||||
<input class="form-check-input" type="checkbox" id="suggestionSourceML" [checked]="useML" [disabled]="useML && !useAI" (change)="setSources($event.target.checked, useAI)">
|
||||
<label class="form-check-label d-inline-flex align-items-center gap-1" for="suggestionSourceML"><i-bs name="cpu"></i-bs><ng-container i18n>ML</ng-container></label>
|
||||
</div>
|
||||
<div class="form-check small">
|
||||
<input class="form-check-input" type="checkbox" id="suggestionSourceAI" [checked]="useAI" [disabled]="useAI && !useML" (change)="setSources(useML, $event.target.checked)">
|
||||
<label class="form-check-label d-inline-flex align-items-center gap-1" for="suggestionSourceAI"><i-bs name="stars"></i-bs><ng-container i18n>AI</ng-container></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+4
@@ -1,3 +1,7 @@
|
||||
.suggestions-dropdown {
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.btn-link.dropdown-toggle::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
+59
-1
@@ -1,6 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { SuggestionSource } from 'src/app/data/ui-settings'
|
||||
import { SuggestionsDropdownComponent } from './suggestions-dropdown.component'
|
||||
|
||||
describe('SuggestionsDropdownComponent', () => {
|
||||
@@ -179,14 +180,71 @@ describe('SuggestionsDropdownComponent', () => {
|
||||
|
||||
it('should toggle dropdown when clickSuggest is called and suggestions are not null', () => {
|
||||
fixture.componentRef.setInput('aiEnabled', true)
|
||||
fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
|
||||
fixture.detectChanges()
|
||||
fixture.componentRef.setInput('suggestions', {
|
||||
suggested_correspondents: [],
|
||||
suggested_tags: [],
|
||||
suggested_document_types: [],
|
||||
})
|
||||
fixture.detectChanges()
|
||||
component.clickSuggest()
|
||||
expect(component.dropdown.open).toBeTruthy()
|
||||
expect(component.dropdown.isOpen()).toBeTruthy()
|
||||
expect(fixture.nativeElement.textContent).toContain('No novel suggestions')
|
||||
})
|
||||
|
||||
it('should fetch unfetched sources and show existing suggestions', () => {
|
||||
jest.spyOn(component.getSuggestions, 'emit')
|
||||
fixture.componentRef.setInput('aiEnabled', true)
|
||||
fixture.componentRef.setInput('source', SuggestionSource.Both)
|
||||
fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
|
||||
fixture.componentRef.setInput('suggestions', { tags: [1] })
|
||||
fixture.detectChanges()
|
||||
component.clickSuggest()
|
||||
expect(component.getSuggestions.emit).toHaveBeenCalledWith(
|
||||
SuggestionSource.Both
|
||||
)
|
||||
expect(component.dropdown.isOpen()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should only show source options when AI is enabled', () => {
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('#suggestionSourceML')
|
||||
).toBeNull()
|
||||
fixture.componentRef.setInput('aiEnabled', true)
|
||||
fixture.detectChanges()
|
||||
fixture.nativeElement
|
||||
.querySelector('button[title="Suggestion options"]')
|
||||
.click()
|
||||
fixture.detectChanges()
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('#suggestionSourceML')
|
||||
).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should emit source changes and never allow no source', () => {
|
||||
const emitSpy = jest.spyOn(component.sourceChange, 'emit')
|
||||
component.setSources(true, true)
|
||||
expect(emitSpy).toHaveBeenCalledWith(SuggestionSource.Both)
|
||||
component.setSources(true, false)
|
||||
expect(emitSpy).toHaveBeenCalledWith(SuggestionSource.ML)
|
||||
component.setSources(false, true)
|
||||
expect(emitSpy).toHaveBeenCalledWith(SuggestionSource.AI)
|
||||
|
||||
emitSpy.mockClear()
|
||||
component.setSources(false, false)
|
||||
expect(emitSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should indicate a non-default source', () => {
|
||||
fixture.componentRef.setInput('aiEnabled', true)
|
||||
fixture.componentRef.setInput('source', SuggestionSource.AI)
|
||||
fixture.componentRef.setInput('defaultSource', SuggestionSource.AI)
|
||||
fixture.detectChanges()
|
||||
expect(fixture.nativeElement.textContent).not.toContain('Not using default')
|
||||
|
||||
fixture.componentRef.setInput('source', SuggestionSource.Both)
|
||||
fixture.detectChanges()
|
||||
expect(fixture.nativeElement.textContent).toContain('Not using default')
|
||||
})
|
||||
})
|
||||
|
||||
+40
-3
@@ -8,6 +8,7 @@ import {
|
||||
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { DocumentSuggestions } from 'src/app/data/document-suggestions'
|
||||
import { SuggestionSource } from 'src/app/data/ui-settings'
|
||||
import { pngxPopperOptions } from 'src/app/utils/popper-options'
|
||||
|
||||
@Component({
|
||||
@@ -18,12 +19,16 @@ import { pngxPopperOptions } from 'src/app/utils/popper-options'
|
||||
})
|
||||
export class SuggestionsDropdownComponent {
|
||||
public popperOptions = pngxPopperOptions
|
||||
public readonly SuggestionSource = SuggestionSource
|
||||
|
||||
@ViewChild('dropdown') dropdown: NgbDropdown
|
||||
readonly suggestions = input<DocumentSuggestions>(null)
|
||||
readonly aiEnabled = input(false)
|
||||
readonly loading = input(false)
|
||||
readonly disabled = input(false)
|
||||
readonly source = input<SuggestionSource>(SuggestionSource.ML)
|
||||
readonly defaultSource = input<SuggestionSource>(SuggestionSource.ML)
|
||||
readonly fetchedSources = input<SuggestionSource[]>([])
|
||||
|
||||
readonly appliedTags = input<number[]>([])
|
||||
readonly appliedCorrespondent = input<number>(null)
|
||||
@@ -31,8 +36,10 @@ export class SuggestionsDropdownComponent {
|
||||
readonly appliedStoragePath = input<number>(null)
|
||||
|
||||
@Output()
|
||||
getSuggestions: EventEmitter<SuggestionsDropdownComponent> =
|
||||
new EventEmitter()
|
||||
getSuggestions: EventEmitter<SuggestionSource> = new EventEmitter()
|
||||
|
||||
@Output()
|
||||
sourceChange: EventEmitter<SuggestionSource> = new EventEmitter()
|
||||
|
||||
@Output()
|
||||
addTag: EventEmitter<string> = new EventEmitter()
|
||||
@@ -53,12 +60,42 @@ export class SuggestionsDropdownComponent {
|
||||
}
|
||||
|
||||
if (!this.suggestions()) {
|
||||
this.getSuggestions.emit(this)
|
||||
this.getSuggestions.emit(this.source())
|
||||
} else if (this.hasUnfetchedSources) {
|
||||
// sources changed, fetch the rest and show what we have meanwhile
|
||||
this.getSuggestions.emit(this.source())
|
||||
this.dropdown?.open()
|
||||
} else {
|
||||
this.dropdown?.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
get useML(): boolean {
|
||||
return this.source() !== SuggestionSource.AI
|
||||
}
|
||||
|
||||
get useAI(): boolean {
|
||||
return this.source() !== SuggestionSource.ML
|
||||
}
|
||||
|
||||
get hasUnfetchedSources(): boolean {
|
||||
const fetched = this.fetchedSources()
|
||||
return (
|
||||
(this.useML && !fetched.includes(SuggestionSource.ML)) ||
|
||||
(this.useAI && !fetched.includes(SuggestionSource.AI))
|
||||
)
|
||||
}
|
||||
|
||||
public setSources(ml: boolean, ai: boolean) {
|
||||
if (ml && ai) {
|
||||
this.sourceChange.emit(SuggestionSource.Both)
|
||||
} else if (ml) {
|
||||
this.sourceChange.emit(SuggestionSource.ML)
|
||||
} else if (ai) {
|
||||
this.sourceChange.emit(SuggestionSource.AI)
|
||||
}
|
||||
}
|
||||
|
||||
get novelSuggestions(): number {
|
||||
return (
|
||||
(this.suggestions()?.suggested_correspondents?.length ?? 0) +
|
||||
|
||||
@@ -134,11 +134,15 @@
|
||||
[loading]="suggestionsLoading()"
|
||||
[suggestions]="suggestions()"
|
||||
[aiEnabled]="aiEnabled"
|
||||
[source]="suggestionSource"
|
||||
[defaultSource]="defaultSuggestionSource"
|
||||
[fetchedSources]="fetchedSuggestionSources()"
|
||||
[appliedTags]="documentForm.value.tags"
|
||||
[appliedCorrespondent]="documentForm.value.correspondent"
|
||||
[appliedDocumentType]="documentForm.value.document_type"
|
||||
[appliedStoragePath]="documentForm.value.storage_path"
|
||||
(getSuggestions)="getSuggestions()"
|
||||
(getSuggestions)="getSuggestions($event)"
|
||||
(sourceChange)="suggestionSourceOverride.set($event)"
|
||||
(addTag)="createTag($event)"
|
||||
(addDocumentType)="createDocumentType($event)"
|
||||
(addCorrespondent)="createCorrespondent($event)">
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
} from 'src/app/data/filter-rule-type'
|
||||
import { StoragePath } from 'src/app/data/storage-path'
|
||||
import { Tag } from 'src/app/data/tag'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||
import { SETTINGS_KEYS, SuggestionSource } from 'src/app/data/ui-settings'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||
@@ -1528,6 +1528,113 @@ describe('DocumentDetailComponent', () => {
|
||||
expect(component.suggestionsLoading()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should get and merge ML and AI suggestions when source is both', () => {
|
||||
settingsService.set(
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
|
||||
SuggestionSource.Both
|
||||
)
|
||||
const getSetting = settingsService.get.bind(settingsService)
|
||||
jest
|
||||
.spyOn(settingsService, 'get')
|
||||
.mockImplementation((key) =>
|
||||
key === SETTINGS_KEYS.AI_ENABLED ? true : getSetting(key)
|
||||
)
|
||||
const suggestionsSpy = jest
|
||||
.spyOn(documentService, 'getSuggestions')
|
||||
.mockReturnValue(of({ tags: [42], dates: ['2024-01-01'] }))
|
||||
const aiSuggestionsSpy = jest
|
||||
.spyOn(documentService, 'getAiSuggestions')
|
||||
.mockReturnValue(
|
||||
of({ title: 'AI title', tags: [42, 43], suggested_tags: ['New'] })
|
||||
)
|
||||
initNormally()
|
||||
expect(suggestionsSpy).toHaveBeenCalled()
|
||||
expect(aiSuggestionsSpy).toHaveBeenCalled()
|
||||
expect(component.suggestions().title).toEqual('AI title')
|
||||
expect(component.suggestions().tags).toEqual([42, 43])
|
||||
expect(component.suggestions().suggested_tags).toEqual(['New'])
|
||||
expect(component.suggestions().dates).toEqual(['2024-01-01'])
|
||||
})
|
||||
|
||||
it('should only fetch sources not yet fetched for the document', () => {
|
||||
settingsService.set(SETTINGS_KEYS.DOCUMENT_EDITING_AUTO_SUGGEST, false)
|
||||
settingsService.set(
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
|
||||
SuggestionSource.ML
|
||||
)
|
||||
const getSetting = settingsService.get.bind(settingsService)
|
||||
jest
|
||||
.spyOn(settingsService, 'get')
|
||||
.mockImplementation((key) =>
|
||||
key === SETTINGS_KEYS.AI_ENABLED ? true : getSetting(key)
|
||||
)
|
||||
const suggestionsSpy = jest
|
||||
.spyOn(documentService, 'getSuggestions')
|
||||
.mockReturnValue(of({ tags: [42] }))
|
||||
const aiSuggestionsSpy = jest
|
||||
.spyOn(documentService, 'getAiSuggestions')
|
||||
.mockReturnValue(of({ tags: [43] }))
|
||||
initNormally()
|
||||
|
||||
component.getSuggestions()
|
||||
expect(suggestionsSpy).toHaveBeenCalledTimes(1)
|
||||
expect(aiSuggestionsSpy).not.toHaveBeenCalled()
|
||||
|
||||
component.getSuggestions(SuggestionSource.Both)
|
||||
expect(suggestionsSpy).toHaveBeenCalledTimes(1)
|
||||
expect(aiSuggestionsSpy).toHaveBeenCalledTimes(1)
|
||||
expect(component.suggestions().tags).toEqual([42, 43])
|
||||
|
||||
component.getSuggestions(SuggestionSource.Both)
|
||||
expect(suggestionsSpy).toHaveBeenCalledTimes(1)
|
||||
expect(aiSuggestionsSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should use the per-document source override and reset it on document change', () => {
|
||||
settingsService.set(SETTINGS_KEYS.DOCUMENT_EDITING_AUTO_SUGGEST, false)
|
||||
const getSetting = settingsService.get.bind(settingsService)
|
||||
jest
|
||||
.spyOn(settingsService, 'get')
|
||||
.mockImplementation((key) =>
|
||||
key === SETTINGS_KEYS.AI_ENABLED ? true : getSetting(key)
|
||||
)
|
||||
initNormally()
|
||||
expect(component.suggestionSource).toEqual(SuggestionSource.AI)
|
||||
component.suggestionSourceOverride.set(SuggestionSource.ML)
|
||||
expect(component.suggestionSource).toEqual(SuggestionSource.ML)
|
||||
|
||||
jest
|
||||
.spyOn(documentService, 'get')
|
||||
.mockReturnValueOnce(of(Object.assign({}, doc)))
|
||||
;(component as any).loadDocument(doc.id, true)
|
||||
expect(component.suggestionSourceOverride()).toBeNull()
|
||||
expect(component.fetchedSuggestionSources()).toEqual([])
|
||||
})
|
||||
|
||||
it('should keep suggestions from one source if the other fails', () => {
|
||||
settingsService.set(
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
|
||||
SuggestionSource.Both
|
||||
)
|
||||
const getSetting = settingsService.get.bind(settingsService)
|
||||
jest
|
||||
.spyOn(settingsService, 'get')
|
||||
.mockImplementation((key) =>
|
||||
key === SETTINGS_KEYS.AI_ENABLED ? true : getSetting(key)
|
||||
)
|
||||
const errorSpy = jest.spyOn(toastService, 'showError')
|
||||
jest
|
||||
.spyOn(documentService, 'getSuggestions')
|
||||
.mockReturnValue(of({ tags: [42] }))
|
||||
jest
|
||||
.spyOn(documentService, 'getAiSuggestions')
|
||||
.mockReturnValue(throwError(() => new Error('failed')))
|
||||
initNormally()
|
||||
expect(errorSpy).toHaveBeenCalled()
|
||||
expect(component.suggestions().tags).toEqual([42])
|
||||
expect(component.fetchedSuggestionSources()).toEqual([SuggestionSource.ML])
|
||||
})
|
||||
|
||||
it('should show error if needed for get suggestions', () => {
|
||||
const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions')
|
||||
const errorSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { DeviceDetectorService } from 'ngx-device-detector'
|
||||
import { BehaviorSubject, Observable, of, Subject, timer } from 'rxjs'
|
||||
import { BehaviorSubject, merge, Observable, of, Subject, timer } from 'rxjs'
|
||||
import {
|
||||
catchError,
|
||||
debounceTime,
|
||||
@@ -48,7 +48,10 @@ import { DataType } from 'src/app/data/datatype'
|
||||
import { Document, DocumentVersionInfo } from 'src/app/data/document'
|
||||
import { DocumentMetadata } from 'src/app/data/document-metadata'
|
||||
import { DocumentNote } from 'src/app/data/document-note'
|
||||
import { DocumentSuggestions } from 'src/app/data/document-suggestions'
|
||||
import {
|
||||
DocumentSuggestions,
|
||||
mergeSuggestions,
|
||||
} from 'src/app/data/document-suggestions'
|
||||
import { DocumentType } from 'src/app/data/document-type'
|
||||
import { FilterRule } from 'src/app/data/filter-rule'
|
||||
import {
|
||||
@@ -63,7 +66,7 @@ import {
|
||||
import { ObjectWithId } from 'src/app/data/object-with-id'
|
||||
import { StoragePath } from 'src/app/data/storage-path'
|
||||
import { Tag } from 'src/app/data/tag'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||
import { SETTINGS_KEYS, SuggestionSource } from 'src/app/data/ui-settings'
|
||||
import { User } from 'src/app/data/user'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
@@ -240,6 +243,10 @@ export class DocumentDetailComponent
|
||||
private readonly autoSuggestSetting = this.settings.getSignal<boolean>(
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_AUTO_SUGGEST
|
||||
)
|
||||
private readonly suggestionSourceSetting =
|
||||
this.settings.getSignal<SuggestionSource>(
|
||||
SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE
|
||||
)
|
||||
private readonly hiddenFieldsSetting = this.settings.getSignal<
|
||||
DocumentDetailFieldID[]
|
||||
>(SETTINGS_KEYS.DOCUMENT_DETAILS_HIDDEN_FIELDS)
|
||||
@@ -261,6 +268,9 @@ export class DocumentDetailComponent
|
||||
readonly metadata = signal<DocumentMetadata>(undefined)
|
||||
readonly suggestions = signal<DocumentSuggestions>(undefined)
|
||||
readonly suggestionsLoading = signal(false)
|
||||
// per-document, resets on navigation
|
||||
readonly suggestionSourceOverride = signal<SuggestionSource>(null)
|
||||
readonly fetchedSuggestionSources = signal<SuggestionSource[]>([])
|
||||
readonly users = signal<User[]>(undefined)
|
||||
|
||||
readonly title = signal<string>(undefined)
|
||||
@@ -365,6 +375,15 @@ export class DocumentDetailComponent
|
||||
return this.autoSuggestSetting()
|
||||
}
|
||||
|
||||
get defaultSuggestionSource(): SuggestionSource {
|
||||
return this.aiEnabled ? this.suggestionSourceSetting() : SuggestionSource.ML
|
||||
}
|
||||
|
||||
get suggestionSource(): SuggestionSource {
|
||||
if (!this.aiEnabled) return SuggestionSource.ML
|
||||
return this.suggestionSourceOverride() ?? this.defaultSuggestionSource
|
||||
}
|
||||
|
||||
get archiveContentRenderType(): ContentRenderType {
|
||||
const hasArchiveVersion =
|
||||
this.metadata()?.has_archive_version ??
|
||||
@@ -590,6 +609,8 @@ export class DocumentDetailComponent
|
||||
}
|
||||
this.documentId.set(doc.id)
|
||||
this.suggestions.set(null)
|
||||
this.suggestionSourceOverride.set(null)
|
||||
this.fetchedSuggestionSources.set([])
|
||||
const openDocument = this.openDocumentService.getOpenDocument(
|
||||
this.documentId()
|
||||
)
|
||||
@@ -1077,29 +1098,44 @@ export class DocumentDetailComponent
|
||||
return this.documentForm.get('custom_fields') as FormArray
|
||||
}
|
||||
|
||||
getSuggestions() {
|
||||
getSuggestions(source: SuggestionSource = this.suggestionSource) {
|
||||
const sources = (
|
||||
source === SuggestionSource.Both
|
||||
? [SuggestionSource.ML, SuggestionSource.AI]
|
||||
: [source]
|
||||
).filter((s) => !this.fetchedSuggestionSources().includes(s))
|
||||
if (!sources.length) return
|
||||
|
||||
this.suggestionsLoading.set(true)
|
||||
const suggestionsObservable = this.aiEnabled
|
||||
? this.documentsService.getAiSuggestions(this.documentId())
|
||||
: this.documentsService.getSuggestions(this.documentId())
|
||||
suggestionsObservable
|
||||
merge(
|
||||
...sources.map((s) =>
|
||||
(s === SuggestionSource.AI
|
||||
? this.documentsService.getAiSuggestions(this.documentId())
|
||||
: this.documentsService.getSuggestions(this.documentId())
|
||||
).pipe(
|
||||
first(),
|
||||
map((result) => ({ source: s, result })),
|
||||
catchError((error) => {
|
||||
this.toastService.showError(
|
||||
$localize`Error retrieving suggestions.`,
|
||||
error
|
||||
)
|
||||
return of(null)
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.pipe(
|
||||
first(),
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
takeUntil(this.docChangeNotifier),
|
||||
finalize(() => this.suggestionsLoading.set(false))
|
||||
)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.suggestions.set(result)
|
||||
},
|
||||
error: (error) => {
|
||||
this.suggestions.set(null)
|
||||
this.toastService.showError(
|
||||
$localize`Error retrieving suggestions.`,
|
||||
error
|
||||
)
|
||||
},
|
||||
.subscribe((response) => {
|
||||
if (!response) return
|
||||
this.fetchedSuggestionSources.update((f) => [...f, response.source])
|
||||
this.suggestions.set(
|
||||
mergeSuggestions(this.suggestions(), response.result)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,3 +15,33 @@ export interface DocumentSuggestions {
|
||||
|
||||
dates?: string[] // ISO-formatted date string e.g. 2022-11-03
|
||||
}
|
||||
|
||||
const union = <T>(a: T[] = [], b: T[] = []): T[] => [...new Set([...a, ...b])]
|
||||
|
||||
export function mergeSuggestions(
|
||||
a: DocumentSuggestions,
|
||||
b: DocumentSuggestions
|
||||
): DocumentSuggestions {
|
||||
if (!a) return b
|
||||
return {
|
||||
title: a.title || b.title,
|
||||
tags: union(a.tags, b.tags),
|
||||
suggested_tags: union(a.suggested_tags, b.suggested_tags),
|
||||
correspondents: union(a.correspondents, b.correspondents),
|
||||
suggested_correspondents: union(
|
||||
a.suggested_correspondents,
|
||||
b.suggested_correspondents
|
||||
),
|
||||
document_types: union(a.document_types, b.document_types),
|
||||
suggested_document_types: union(
|
||||
a.suggested_document_types,
|
||||
b.suggested_document_types
|
||||
),
|
||||
storage_paths: union(a.storage_paths, b.storage_paths),
|
||||
suggested_storage_paths: union(
|
||||
a.suggested_storage_paths,
|
||||
b.suggested_storage_paths
|
||||
),
|
||||
dates: union(a.dates, b.dates),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ export enum GlobalSearchType {
|
||||
TITLE_CONTENT = 'title-content',
|
||||
}
|
||||
|
||||
export enum SuggestionSource {
|
||||
ML = 'ml',
|
||||
AI = 'ai',
|
||||
Both = 'both',
|
||||
}
|
||||
|
||||
export enum CollapsibleSection {
|
||||
ATTRIBUTES = 'attributes',
|
||||
}
|
||||
@@ -98,6 +104,8 @@ export const SETTINGS_KEYS = {
|
||||
'general-settings:document-editing:overlay-thumbnail',
|
||||
DOCUMENT_EDITING_AUTO_SUGGEST:
|
||||
'general-settings:document-editing:auto-suggest',
|
||||
DOCUMENT_EDITING_SUGGESTION_SOURCE:
|
||||
'general-settings:document-editing:suggestion-source',
|
||||
DOCUMENT_DETAILS_HIDDEN_FIELDS:
|
||||
'general-settings:document-details:hidden-fields',
|
||||
SEARCH_DB_ONLY: 'general-settings:search:db-only',
|
||||
@@ -326,6 +334,11 @@ export const SETTINGS: UiSetting[] = [
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
key: SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
|
||||
type: 'string',
|
||||
default: SuggestionSource.AI,
|
||||
},
|
||||
{
|
||||
key: SETTINGS_KEYS.DOCUMENT_DETAILS_HIDDEN_FIELDS,
|
||||
type: 'array',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -74,6 +74,7 @@ import {
|
||||
clipboardCheckFill,
|
||||
clipboardFill,
|
||||
clockHistory,
|
||||
cpu,
|
||||
creditCard,
|
||||
dash,
|
||||
dashCircle,
|
||||
@@ -118,6 +119,7 @@ import {
|
||||
infoCircle,
|
||||
journalBookmarkFill,
|
||||
journals,
|
||||
lightbulb,
|
||||
link,
|
||||
list,
|
||||
listNested,
|
||||
@@ -322,6 +324,7 @@ const icons = {
|
||||
clipboardCheckFill,
|
||||
clipboardFill,
|
||||
clockHistory,
|
||||
cpu,
|
||||
cash,
|
||||
creditCard,
|
||||
dash,
|
||||
@@ -367,6 +370,7 @@ const icons = {
|
||||
infoCircle,
|
||||
journalBookmarkFill,
|
||||
journals,
|
||||
lightbulb,
|
||||
link,
|
||||
list,
|
||||
listNested,
|
||||
|
||||
@@ -292,6 +292,7 @@ a.btn-link:active,
|
||||
a.btn-link:focus-visible,
|
||||
.btn-link:hover,
|
||||
.btn-link:active,
|
||||
.btn-link.show,
|
||||
.btn-link:focus-visible {
|
||||
color: var(--pngx-primary-lighten-10) !important;
|
||||
.primary-light & {
|
||||
|
||||
@@ -25,10 +25,20 @@ export class PDFFindController {
|
||||
onIsPageVisible?: () => boolean
|
||||
}
|
||||
|
||||
export const LinkTarget = {
|
||||
NONE: 0,
|
||||
SELF: 1,
|
||||
BLANK: 2,
|
||||
PARENT: 3,
|
||||
TOP: 4,
|
||||
}
|
||||
|
||||
export class PDFLinkService {
|
||||
private document?: unknown
|
||||
private viewer?: unknown
|
||||
|
||||
constructor(readonly options: Record<string, unknown> = {}) {}
|
||||
|
||||
setDocument(document: unknown): void {
|
||||
this.document = document
|
||||
}
|
||||
|
||||
@@ -1079,6 +1079,16 @@ class DocumentSerializer(
|
||||
)
|
||||
|
||||
def get_page_count(self, obj) -> int | None:
|
||||
# Like content versions get their own page count from the newest version,
|
||||
# use the prefetched versions cache to avoid an extra query
|
||||
prefetched_cache = getattr(obj, "_prefetched_objects_cache", None)
|
||||
prefetched_versions = (
|
||||
prefetched_cache.get("versions")
|
||||
if isinstance(prefetched_cache, dict)
|
||||
else None
|
||||
)
|
||||
if obj.root_document_id is None and prefetched_versions:
|
||||
return sort_versions_newest_first(prefetched_versions)[0].page_count
|
||||
return obj.page_count
|
||||
|
||||
@extend_schema_field(DuplicateDocumentSummarySerializer(many=True))
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<meta name="author" content="The Paperless-ngx Team">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="manifest" href="{% static webmanifest %}">
|
||||
<link rel="manifest" href="{% static webmanifest %}" crossorigin="use-credentials">
|
||||
<link rel="stylesheet" href="{% static styles_css %}">
|
||||
<link rel="apple-touch-icon" href="{% static apple_touch_icon %}">
|
||||
</head>
|
||||
|
||||
@@ -19,6 +19,7 @@ from documents.models import Document
|
||||
from documents.versioning import annotate_effective_content
|
||||
from documents.views import DocumentSelectionMixin
|
||||
from paperless_testing.dirs import DirectoriesMixin
|
||||
from paperless_testing.factories import DocumentFactory
|
||||
from paperless_testing.factories import UserFactory
|
||||
from paperless_testing.http import read_streaming_response
|
||||
from paperless_testing.permissions import grant_global
|
||||
@@ -821,6 +822,26 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["content"], "v1-content")
|
||||
|
||||
def test_page_count_resolves_to_latest_version(self) -> None:
|
||||
root = DocumentFactory(page_count=2)
|
||||
DocumentFactory(root_document=root, version_index=1, page_count=1)
|
||||
unversioned = DocumentFactory(page_count=5)
|
||||
|
||||
resp = self.client.get("/api/documents/?fields=id,page_count")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
{doc["id"]: doc["page_count"] for doc in resp.data["results"]},
|
||||
{root.id: 1, unversioned.id: 5},
|
||||
)
|
||||
|
||||
resp = self.client.get(f"/api/documents/{root.id}/")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["page_count"], 1)
|
||||
|
||||
resp = self.client.get(f"/api/documents/{root.id}/?version={root.id}")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["page_count"], 2)
|
||||
|
||||
def _make_root_with_out_of_order_versions(self) -> tuple[Document, ...]:
|
||||
"""
|
||||
A root whose newest version has a *lower* id than an older one, which is
|
||||
|
||||
@@ -1192,6 +1192,7 @@ class DocumentViewSet(
|
||||
"version_label",
|
||||
"root_document_id",
|
||||
"version_index",
|
||||
"page_count",
|
||||
),
|
||||
),
|
||||
"tags",
|
||||
@@ -1271,13 +1272,16 @@ class DocumentViewSet(
|
||||
if (
|
||||
"version" not in request.query_params
|
||||
or not isinstance(response.data, dict)
|
||||
or "content" not in response.data
|
||||
or not ({"content", "page_count"} & response.data.keys())
|
||||
):
|
||||
return response
|
||||
|
||||
root_doc = self.get_object()
|
||||
content_doc = self._resolve_file_doc(root_doc, request)
|
||||
response.data["content"] = content_doc.content or ""
|
||||
if "content" in response.data:
|
||||
response.data["content"] = content_doc.content or ""
|
||||
if "page_count" in response.data:
|
||||
response.data["page_count"] = content_doc.page_count
|
||||
return response
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-18 01:29+0000\n"
|
||||
"PO-Revision-Date: 2026-09-18 01:30\n"
|
||||
"POT-Creation-Date: 2026-10-02 23:09+0000\n"
|
||||
"PO-Revision-Date: 2026-10-02 23:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Afrikaans\n"
|
||||
"Language: af_ZA\n"
|
||||
@@ -1615,7 +1615,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:514 documents/serialisers.py:871
|
||||
#: documents/serialisers.py:2883 documents/views.py:343 documents/views.py:2726
|
||||
#: documents/serialisers.py:2895 documents/views.py:342 documents/views.py:2729
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1624,38 +1624,38 @@ msgstr ""
|
||||
msgid "Invalid color."
|
||||
msgstr "Ongeldige kleur."
|
||||
|
||||
#: documents/serialisers.py:2350
|
||||
#: documents/serialisers.py:2362
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Lêertipe %(type)s word nie ondersteun nie"
|
||||
|
||||
#: documents/serialisers.py:2394
|
||||
#: documents/serialisers.py:2406
|
||||
#, python-format
|
||||
msgid "Custom field id must be an integer: %(id)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2401
|
||||
#: documents/serialisers.py:2413
|
||||
#, python-format
|
||||
msgid "Custom field with id %(id)s does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2418 documents/serialisers.py:2428
|
||||
#: documents/serialisers.py:2430 documents/serialisers.py:2440
|
||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2423
|
||||
#: documents/serialisers.py:2435
|
||||
msgid "Some custom fields don't exist or were specified twice."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2570
|
||||
#: documents/serialisers.py:2582
|
||||
msgid "Invalid variable detected."
|
||||
msgstr "Ongeldige veranderlike bespeur."
|
||||
|
||||
#: documents/serialisers.py:2939
|
||||
#: documents/serialisers.py:2951
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2969 documents/views.py:4763
|
||||
#: documents/serialisers.py:2981 documents/views.py:4784
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1910,40 +1910,40 @@ msgstr ""
|
||||
msgid "Unable to parse URI {value}"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:336 documents/views.py:2723
|
||||
#: documents/views.py:335 documents/views.py:2726
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1670
|
||||
#: documents/views.py:1673
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1681
|
||||
#: documents/views.py:1684
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1693
|
||||
#: documents/views.py:1696
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:2548 documents/views.py:2864
|
||||
#: documents/views.py:2551 documents/views.py:2867
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4776
|
||||
#: documents/views.py:4797
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4822
|
||||
#: documents/views.py:4843
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4886
|
||||
#: documents/views.py:4907
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4900
|
||||
#: documents/views.py:4921
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -2188,194 +2188,198 @@ msgid "Sets the LLM embedding model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:369
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgid "Sets the LLM embedding API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:376
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:383
|
||||
msgid "Sets the LLM embedding chunk size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:382
|
||||
#: paperless/models.py:389
|
||||
msgid "Sets the LLM context size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:388
|
||||
#: paperless/models.py:395
|
||||
msgid "Sets the LLM backend"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:396
|
||||
#: paperless/models.py:403
|
||||
msgid "Sets the LLM model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:403
|
||||
#: paperless/models.py:410
|
||||
msgid "Sets the LLM API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:410
|
||||
#: paperless/models.py:417
|
||||
msgid "Sets the LLM endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:417
|
||||
#: paperless/models.py:424
|
||||
msgid "Sets the LLM output language"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:424
|
||||
#: paperless/models.py:431
|
||||
msgid "Sets the LLM timeout in seconds"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:430
|
||||
#: paperless/models.py:437
|
||||
msgid "paperless application settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:558
|
||||
#: paperless/settings/__init__.py:560
|
||||
msgid "English (US)"
|
||||
msgstr "Engels (VS)"
|
||||
|
||||
#: paperless/settings/__init__.py:559
|
||||
#: paperless/settings/__init__.py:561
|
||||
msgid "Arabic"
|
||||
msgstr "Arabies"
|
||||
|
||||
#: paperless/settings/__init__.py:560
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "Afrikaans"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:561
|
||||
#: paperless/settings/__init__.py:563
|
||||
msgid "Belarusian"
|
||||
msgstr "Belorussies"
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:563
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Catalan"
|
||||
msgstr "Katalaans"
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
#: paperless/settings/__init__.py:566
|
||||
msgid "Czech"
|
||||
msgstr "Tsjeggies"
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Danish"
|
||||
msgstr "Deens"
|
||||
|
||||
#: paperless/settings/__init__.py:566
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "German"
|
||||
msgstr "Duits"
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
#: paperless/settings/__init__.py:569
|
||||
msgid "Greek"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:568
|
||||
#: paperless/settings/__init__.py:570
|
||||
msgid "English (GB)"
|
||||
msgstr "Engels (GB)"
|
||||
|
||||
#: paperless/settings/__init__.py:569
|
||||
#: paperless/settings/__init__.py:571
|
||||
msgid "Spanish"
|
||||
msgstr "Spaans"
|
||||
|
||||
#: paperless/settings/__init__.py:570
|
||||
#: paperless/settings/__init__.py:572
|
||||
msgid "Persian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:571
|
||||
#: paperless/settings/__init__.py:573
|
||||
msgid "Finnish"
|
||||
msgstr "Fins"
|
||||
|
||||
#: paperless/settings/__init__.py:572
|
||||
#: paperless/settings/__init__.py:574
|
||||
msgid "French"
|
||||
msgstr "Frans"
|
||||
|
||||
#: paperless/settings/__init__.py:573
|
||||
#: paperless/settings/__init__.py:575
|
||||
msgid "Hungarian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:574
|
||||
#: paperless/settings/__init__.py:576
|
||||
msgid "Indonesian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:575
|
||||
#: paperless/settings/__init__.py:577
|
||||
msgid "Italian"
|
||||
msgstr "Italiaans"
|
||||
|
||||
#: paperless/settings/__init__.py:576
|
||||
#: paperless/settings/__init__.py:578
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:577
|
||||
#: paperless/settings/__init__.py:579
|
||||
msgid "Korean"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:578
|
||||
#: paperless/settings/__init__.py:580
|
||||
msgid "Luxembourgish"
|
||||
msgstr "Luxemburgs"
|
||||
|
||||
#: paperless/settings/__init__.py:579
|
||||
#: paperless/settings/__init__.py:581
|
||||
msgid "Norwegian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:580
|
||||
#: paperless/settings/__init__.py:582
|
||||
msgid "Dutch"
|
||||
msgstr "Nederlands"
|
||||
|
||||
#: paperless/settings/__init__.py:581
|
||||
#: paperless/settings/__init__.py:583
|
||||
msgid "Polish"
|
||||
msgstr "Pools"
|
||||
|
||||
#: paperless/settings/__init__.py:582
|
||||
#: paperless/settings/__init__.py:584
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugees (Brasilië)"
|
||||
|
||||
#: paperless/settings/__init__.py:583
|
||||
#: paperless/settings/__init__.py:585
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugees"
|
||||
|
||||
#: paperless/settings/__init__.py:584
|
||||
#: paperless/settings/__init__.py:586
|
||||
msgid "Romanian"
|
||||
msgstr "Roemeens"
|
||||
|
||||
#: paperless/settings/__init__.py:585
|
||||
#: paperless/settings/__init__.py:587
|
||||
msgid "Russian"
|
||||
msgstr "Russies"
|
||||
|
||||
#: paperless/settings/__init__.py:586
|
||||
#: paperless/settings/__init__.py:588
|
||||
msgid "Slovak"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:587
|
||||
#: paperless/settings/__init__.py:589
|
||||
msgid "Slovenian"
|
||||
msgstr "Sloweens"
|
||||
|
||||
#: paperless/settings/__init__.py:588
|
||||
#: paperless/settings/__init__.py:590
|
||||
msgid "Serbian"
|
||||
msgstr "Serwies"
|
||||
|
||||
#: paperless/settings/__init__.py:589
|
||||
#: paperless/settings/__init__.py:591
|
||||
msgid "Swedish"
|
||||
msgstr "Sweeds"
|
||||
|
||||
#: paperless/settings/__init__.py:590
|
||||
#: paperless/settings/__init__.py:592
|
||||
msgid "Turkish"
|
||||
msgstr "Turks"
|
||||
|
||||
#: paperless/settings/__init__.py:591
|
||||
#: paperless/settings/__init__.py:593
|
||||
msgid "Ukrainian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:592
|
||||
#: paperless/settings/__init__.py:594
|
||||
msgid "Vietnamese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:593
|
||||
#: paperless/settings/__init__.py:595
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "Vereenvoudigde Sjinees"
|
||||
|
||||
#: paperless/settings/__init__.py:594
|
||||
#: paperless/settings/__init__.py:596
|
||||
msgid "Chinese Traditional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:435
|
||||
#: paperless/urls.py:438
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Paperless-ngx administrasie"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-18 01:29+0000\n"
|
||||
"PO-Revision-Date: 2026-09-18 01:30\n"
|
||||
"POT-Creation-Date: 2026-10-02 23:09+0000\n"
|
||||
"PO-Revision-Date: 2026-10-02 23:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Amharic\n"
|
||||
"Language: am_ET\n"
|
||||
@@ -1615,7 +1615,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:514 documents/serialisers.py:871
|
||||
#: documents/serialisers.py:2883 documents/views.py:343 documents/views.py:2726
|
||||
#: documents/serialisers.py:2895 documents/views.py:342 documents/views.py:2729
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1624,38 +1624,38 @@ msgstr ""
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2350
|
||||
#: documents/serialisers.py:2362
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2394
|
||||
#: documents/serialisers.py:2406
|
||||
#, python-format
|
||||
msgid "Custom field id must be an integer: %(id)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2401
|
||||
#: documents/serialisers.py:2413
|
||||
#, python-format
|
||||
msgid "Custom field with id %(id)s does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2418 documents/serialisers.py:2428
|
||||
#: documents/serialisers.py:2430 documents/serialisers.py:2440
|
||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2423
|
||||
#: documents/serialisers.py:2435
|
||||
msgid "Some custom fields don't exist or were specified twice."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2570
|
||||
#: documents/serialisers.py:2582
|
||||
msgid "Invalid variable detected."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2939
|
||||
#: documents/serialisers.py:2951
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2969 documents/views.py:4763
|
||||
#: documents/serialisers.py:2981 documents/views.py:4784
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1910,40 +1910,40 @@ msgstr ""
|
||||
msgid "Unable to parse URI {value}"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:336 documents/views.py:2723
|
||||
#: documents/views.py:335 documents/views.py:2726
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1670
|
||||
#: documents/views.py:1673
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1681
|
||||
#: documents/views.py:1684
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1693
|
||||
#: documents/views.py:1696
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:2548 documents/views.py:2864
|
||||
#: documents/views.py:2551 documents/views.py:2867
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4776
|
||||
#: documents/views.py:4797
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4822
|
||||
#: documents/views.py:4843
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4886
|
||||
#: documents/views.py:4907
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4900
|
||||
#: documents/views.py:4921
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -2188,194 +2188,198 @@ msgid "Sets the LLM embedding model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:369
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgid "Sets the LLM embedding API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:376
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:383
|
||||
msgid "Sets the LLM embedding chunk size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:382
|
||||
#: paperless/models.py:389
|
||||
msgid "Sets the LLM context size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:388
|
||||
#: paperless/models.py:395
|
||||
msgid "Sets the LLM backend"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:396
|
||||
#: paperless/models.py:403
|
||||
msgid "Sets the LLM model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:403
|
||||
#: paperless/models.py:410
|
||||
msgid "Sets the LLM API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:410
|
||||
#: paperless/models.py:417
|
||||
msgid "Sets the LLM endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:417
|
||||
#: paperless/models.py:424
|
||||
msgid "Sets the LLM output language"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:424
|
||||
#: paperless/models.py:431
|
||||
msgid "Sets the LLM timeout in seconds"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:430
|
||||
#: paperless/models.py:437
|
||||
msgid "paperless application settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:558
|
||||
#: paperless/settings/__init__.py:560
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:559
|
||||
#: paperless/settings/__init__.py:561
|
||||
msgid "Arabic"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:560
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "Afrikaans"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:561
|
||||
#: paperless/settings/__init__.py:563
|
||||
msgid "Belarusian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:563
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
#: paperless/settings/__init__.py:566
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:566
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
#: paperless/settings/__init__.py:569
|
||||
msgid "Greek"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:568
|
||||
#: paperless/settings/__init__.py:570
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:569
|
||||
#: paperless/settings/__init__.py:571
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:570
|
||||
#: paperless/settings/__init__.py:572
|
||||
msgid "Persian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:571
|
||||
#: paperless/settings/__init__.py:573
|
||||
msgid "Finnish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:572
|
||||
#: paperless/settings/__init__.py:574
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:573
|
||||
#: paperless/settings/__init__.py:575
|
||||
msgid "Hungarian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:574
|
||||
#: paperless/settings/__init__.py:576
|
||||
msgid "Indonesian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:575
|
||||
#: paperless/settings/__init__.py:577
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:576
|
||||
#: paperless/settings/__init__.py:578
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:577
|
||||
#: paperless/settings/__init__.py:579
|
||||
msgid "Korean"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:578
|
||||
#: paperless/settings/__init__.py:580
|
||||
msgid "Luxembourgish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:579
|
||||
#: paperless/settings/__init__.py:581
|
||||
msgid "Norwegian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:580
|
||||
#: paperless/settings/__init__.py:582
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:581
|
||||
#: paperless/settings/__init__.py:583
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:582
|
||||
#: paperless/settings/__init__.py:584
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:583
|
||||
#: paperless/settings/__init__.py:585
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:584
|
||||
#: paperless/settings/__init__.py:586
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:585
|
||||
#: paperless/settings/__init__.py:587
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:586
|
||||
#: paperless/settings/__init__.py:588
|
||||
msgid "Slovak"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:587
|
||||
#: paperless/settings/__init__.py:589
|
||||
msgid "Slovenian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:588
|
||||
#: paperless/settings/__init__.py:590
|
||||
msgid "Serbian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:589
|
||||
#: paperless/settings/__init__.py:591
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:590
|
||||
#: paperless/settings/__init__.py:592
|
||||
msgid "Turkish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:591
|
||||
#: paperless/settings/__init__.py:593
|
||||
msgid "Ukrainian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:592
|
||||
#: paperless/settings/__init__.py:594
|
||||
msgid "Vietnamese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:593
|
||||
#: paperless/settings/__init__.py:595
|
||||
msgid "Chinese Simplified"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:594
|
||||
#: paperless/settings/__init__.py:596
|
||||
msgid "Chinese Traditional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:435
|
||||
#: paperless/urls.py:438
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-18 01:29+0000\n"
|
||||
"PO-Revision-Date: 2026-09-18 01:30\n"
|
||||
"POT-Creation-Date: 2026-10-02 23:09+0000\n"
|
||||
"PO-Revision-Date: 2026-10-02 23:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Arabic\n"
|
||||
"Language: ar_SA\n"
|
||||
@@ -1615,7 +1615,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:514 documents/serialisers.py:871
|
||||
#: documents/serialisers.py:2883 documents/views.py:343 documents/views.py:2726
|
||||
#: documents/serialisers.py:2895 documents/views.py:342 documents/views.py:2729
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1624,38 +1624,38 @@ msgstr ""
|
||||
msgid "Invalid color."
|
||||
msgstr "لون خاطئ."
|
||||
|
||||
#: documents/serialisers.py:2350
|
||||
#: documents/serialisers.py:2362
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "نوع الملف %(type)s غير مدعوم"
|
||||
|
||||
#: documents/serialisers.py:2394
|
||||
#: documents/serialisers.py:2406
|
||||
#, python-format
|
||||
msgid "Custom field id must be an integer: %(id)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2401
|
||||
#: documents/serialisers.py:2413
|
||||
#, python-format
|
||||
msgid "Custom field with id %(id)s does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2418 documents/serialisers.py:2428
|
||||
#: documents/serialisers.py:2430 documents/serialisers.py:2440
|
||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2423
|
||||
#: documents/serialisers.py:2435
|
||||
msgid "Some custom fields don't exist or were specified twice."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2570
|
||||
#: documents/serialisers.py:2582
|
||||
msgid "Invalid variable detected."
|
||||
msgstr "اكتشاف متغير خاطئ."
|
||||
|
||||
#: documents/serialisers.py:2939
|
||||
#: documents/serialisers.py:2951
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2969 documents/views.py:4763
|
||||
#: documents/serialisers.py:2981 documents/views.py:4784
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1911,40 +1911,40 @@ msgstr ""
|
||||
msgid "Unable to parse URI {value}"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:336 documents/views.py:2723
|
||||
#: documents/views.py:335 documents/views.py:2726
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1670
|
||||
#: documents/views.py:1673
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1681
|
||||
#: documents/views.py:1684
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1693
|
||||
#: documents/views.py:1696
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:2548 documents/views.py:2864
|
||||
#: documents/views.py:2551 documents/views.py:2867
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4776
|
||||
#: documents/views.py:4797
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4822
|
||||
#: documents/views.py:4843
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4886
|
||||
#: documents/views.py:4907
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4900
|
||||
#: documents/views.py:4921
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -2189,194 +2189,198 @@ msgid "Sets the LLM embedding model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:369
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgid "Sets the LLM embedding API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:376
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:383
|
||||
msgid "Sets the LLM embedding chunk size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:382
|
||||
#: paperless/models.py:389
|
||||
msgid "Sets the LLM context size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:388
|
||||
#: paperless/models.py:395
|
||||
msgid "Sets the LLM backend"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:396
|
||||
#: paperless/models.py:403
|
||||
msgid "Sets the LLM model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:403
|
||||
#: paperless/models.py:410
|
||||
msgid "Sets the LLM API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:410
|
||||
#: paperless/models.py:417
|
||||
msgid "Sets the LLM endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:417
|
||||
#: paperless/models.py:424
|
||||
msgid "Sets the LLM output language"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:424
|
||||
#: paperless/models.py:431
|
||||
msgid "Sets the LLM timeout in seconds"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:430
|
||||
#: paperless/models.py:437
|
||||
msgid "paperless application settings"
|
||||
msgstr "إعدادات التطبيق paperless"
|
||||
|
||||
#: paperless/settings/__init__.py:558
|
||||
#: paperless/settings/__init__.py:560
|
||||
msgid "English (US)"
|
||||
msgstr "الإنجليزية (الولايات المتحدة)"
|
||||
|
||||
#: paperless/settings/__init__.py:559
|
||||
#: paperless/settings/__init__.py:561
|
||||
msgid "Arabic"
|
||||
msgstr "العربية"
|
||||
|
||||
#: paperless/settings/__init__.py:560
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "Afrikaans"
|
||||
msgstr "اللغة الأفريقانية"
|
||||
|
||||
#: paperless/settings/__init__.py:561
|
||||
#: paperless/settings/__init__.py:563
|
||||
msgid "Belarusian"
|
||||
msgstr "البيلاروسية"
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Bulgarian"
|
||||
msgstr "البلغارية"
|
||||
|
||||
#: paperless/settings/__init__.py:563
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Catalan"
|
||||
msgstr "اللغة الكتالونية"
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
#: paperless/settings/__init__.py:566
|
||||
msgid "Czech"
|
||||
msgstr "التشيكية"
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Danish"
|
||||
msgstr "الدانماركية"
|
||||
|
||||
#: paperless/settings/__init__.py:566
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "German"
|
||||
msgstr "الألمانية"
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
#: paperless/settings/__init__.py:569
|
||||
msgid "Greek"
|
||||
msgstr "اليونانية"
|
||||
|
||||
#: paperless/settings/__init__.py:568
|
||||
#: paperless/settings/__init__.py:570
|
||||
msgid "English (GB)"
|
||||
msgstr "الإنجليزية (المملكة المتحدة)"
|
||||
|
||||
#: paperless/settings/__init__.py:569
|
||||
#: paperless/settings/__init__.py:571
|
||||
msgid "Spanish"
|
||||
msgstr "الإسبانية"
|
||||
|
||||
#: paperless/settings/__init__.py:570
|
||||
#: paperless/settings/__init__.py:572
|
||||
msgid "Persian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:571
|
||||
#: paperless/settings/__init__.py:573
|
||||
msgid "Finnish"
|
||||
msgstr "الفنلندية"
|
||||
|
||||
#: paperless/settings/__init__.py:572
|
||||
#: paperless/settings/__init__.py:574
|
||||
msgid "French"
|
||||
msgstr "الفرنسية"
|
||||
|
||||
#: paperless/settings/__init__.py:573
|
||||
#: paperless/settings/__init__.py:575
|
||||
msgid "Hungarian"
|
||||
msgstr "المجرية"
|
||||
|
||||
#: paperless/settings/__init__.py:574
|
||||
#: paperless/settings/__init__.py:576
|
||||
msgid "Indonesian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:575
|
||||
#: paperless/settings/__init__.py:577
|
||||
msgid "Italian"
|
||||
msgstr "الإيطالية"
|
||||
|
||||
#: paperless/settings/__init__.py:576
|
||||
#: paperless/settings/__init__.py:578
|
||||
msgid "Japanese"
|
||||
msgstr "اليابانية"
|
||||
|
||||
#: paperless/settings/__init__.py:577
|
||||
#: paperless/settings/__init__.py:579
|
||||
msgid "Korean"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:578
|
||||
#: paperless/settings/__init__.py:580
|
||||
msgid "Luxembourgish"
|
||||
msgstr "اللوكسمبرجية"
|
||||
|
||||
#: paperless/settings/__init__.py:579
|
||||
#: paperless/settings/__init__.py:581
|
||||
msgid "Norwegian"
|
||||
msgstr "النرويجية"
|
||||
|
||||
#: paperless/settings/__init__.py:580
|
||||
#: paperless/settings/__init__.py:582
|
||||
msgid "Dutch"
|
||||
msgstr "الهولندية"
|
||||
|
||||
#: paperless/settings/__init__.py:581
|
||||
#: paperless/settings/__init__.py:583
|
||||
msgid "Polish"
|
||||
msgstr "البولندية"
|
||||
|
||||
#: paperless/settings/__init__.py:582
|
||||
#: paperless/settings/__init__.py:584
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "البرتغالية (البرازيل)"
|
||||
|
||||
#: paperless/settings/__init__.py:583
|
||||
#: paperless/settings/__init__.py:585
|
||||
msgid "Portuguese"
|
||||
msgstr "البرتغالية"
|
||||
|
||||
#: paperless/settings/__init__.py:584
|
||||
#: paperless/settings/__init__.py:586
|
||||
msgid "Romanian"
|
||||
msgstr "الرومانية"
|
||||
|
||||
#: paperless/settings/__init__.py:585
|
||||
#: paperless/settings/__init__.py:587
|
||||
msgid "Russian"
|
||||
msgstr "الروسية"
|
||||
|
||||
#: paperless/settings/__init__.py:586
|
||||
#: paperless/settings/__init__.py:588
|
||||
msgid "Slovak"
|
||||
msgstr "السلوفاكية"
|
||||
|
||||
#: paperless/settings/__init__.py:587
|
||||
#: paperless/settings/__init__.py:589
|
||||
msgid "Slovenian"
|
||||
msgstr "السلوفانية"
|
||||
|
||||
#: paperless/settings/__init__.py:588
|
||||
#: paperless/settings/__init__.py:590
|
||||
msgid "Serbian"
|
||||
msgstr "الصربية"
|
||||
|
||||
#: paperless/settings/__init__.py:589
|
||||
#: paperless/settings/__init__.py:591
|
||||
msgid "Swedish"
|
||||
msgstr "السويدية"
|
||||
|
||||
#: paperless/settings/__init__.py:590
|
||||
#: paperless/settings/__init__.py:592
|
||||
msgid "Turkish"
|
||||
msgstr "التركية"
|
||||
|
||||
#: paperless/settings/__init__.py:591
|
||||
#: paperless/settings/__init__.py:593
|
||||
msgid "Ukrainian"
|
||||
msgstr "الأوكرانية"
|
||||
|
||||
#: paperless/settings/__init__.py:592
|
||||
#: paperless/settings/__init__.py:594
|
||||
msgid "Vietnamese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:593
|
||||
#: paperless/settings/__init__.py:595
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "الصينية المبسطة"
|
||||
|
||||
#: paperless/settings/__init__.py:594
|
||||
#: paperless/settings/__init__.py:596
|
||||
msgid "Chinese Traditional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:435
|
||||
#: paperless/urls.py:438
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Paperless-ngx الإدارة"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-18 01:29+0000\n"
|
||||
"PO-Revision-Date: 2026-09-18 01:30\n"
|
||||
"POT-Creation-Date: 2026-10-02 23:09+0000\n"
|
||||
"PO-Revision-Date: 2026-10-02 23:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Belarusian\n"
|
||||
"Language: be_BY\n"
|
||||
@@ -1615,7 +1615,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:514 documents/serialisers.py:871
|
||||
#: documents/serialisers.py:2883 documents/views.py:343 documents/views.py:2726
|
||||
#: documents/serialisers.py:2895 documents/views.py:342 documents/views.py:2729
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1624,38 +1624,38 @@ msgstr ""
|
||||
msgid "Invalid color."
|
||||
msgstr "Няправільны колер."
|
||||
|
||||
#: documents/serialisers.py:2350
|
||||
#: documents/serialisers.py:2362
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Тып файла %(type)s не падтрымліваецца"
|
||||
|
||||
#: documents/serialisers.py:2394
|
||||
#: documents/serialisers.py:2406
|
||||
#, python-format
|
||||
msgid "Custom field id must be an integer: %(id)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2401
|
||||
#: documents/serialisers.py:2413
|
||||
#, python-format
|
||||
msgid "Custom field with id %(id)s does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2418 documents/serialisers.py:2428
|
||||
#: documents/serialisers.py:2430 documents/serialisers.py:2440
|
||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2423
|
||||
#: documents/serialisers.py:2435
|
||||
msgid "Some custom fields don't exist or were specified twice."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2570
|
||||
#: documents/serialisers.py:2582
|
||||
msgid "Invalid variable detected."
|
||||
msgstr "Выяўлена няправільная зменная."
|
||||
|
||||
#: documents/serialisers.py:2939
|
||||
#: documents/serialisers.py:2951
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2969 documents/views.py:4763
|
||||
#: documents/serialisers.py:2981 documents/views.py:4784
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1910,40 +1910,40 @@ msgstr ""
|
||||
msgid "Unable to parse URI {value}"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:336 documents/views.py:2723
|
||||
#: documents/views.py:335 documents/views.py:2726
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1670
|
||||
#: documents/views.py:1673
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1681
|
||||
#: documents/views.py:1684
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1693
|
||||
#: documents/views.py:1696
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:2548 documents/views.py:2864
|
||||
#: documents/views.py:2551 documents/views.py:2867
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4776
|
||||
#: documents/views.py:4797
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4822
|
||||
#: documents/views.py:4843
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4886
|
||||
#: documents/views.py:4907
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4900
|
||||
#: documents/views.py:4921
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -2188,194 +2188,198 @@ msgid "Sets the LLM embedding model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:369
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgid "Sets the LLM embedding API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:376
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:383
|
||||
msgid "Sets the LLM embedding chunk size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:382
|
||||
#: paperless/models.py:389
|
||||
msgid "Sets the LLM context size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:388
|
||||
#: paperless/models.py:395
|
||||
msgid "Sets the LLM backend"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:396
|
||||
#: paperless/models.py:403
|
||||
msgid "Sets the LLM model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:403
|
||||
#: paperless/models.py:410
|
||||
msgid "Sets the LLM API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:410
|
||||
#: paperless/models.py:417
|
||||
msgid "Sets the LLM endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:417
|
||||
#: paperless/models.py:424
|
||||
msgid "Sets the LLM output language"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:424
|
||||
#: paperless/models.py:431
|
||||
msgid "Sets the LLM timeout in seconds"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:430
|
||||
#: paperless/models.py:437
|
||||
msgid "paperless application settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:558
|
||||
#: paperless/settings/__init__.py:560
|
||||
msgid "English (US)"
|
||||
msgstr "Англійская (ЗША)"
|
||||
|
||||
#: paperless/settings/__init__.py:559
|
||||
#: paperless/settings/__init__.py:561
|
||||
msgid "Arabic"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:560
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "Afrikaans"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:561
|
||||
#: paperless/settings/__init__.py:563
|
||||
msgid "Belarusian"
|
||||
msgstr "Беларуская"
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:563
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
#: paperless/settings/__init__.py:566
|
||||
msgid "Czech"
|
||||
msgstr "Чэшская"
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Danish"
|
||||
msgstr "Дацкая"
|
||||
|
||||
#: paperless/settings/__init__.py:566
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "German"
|
||||
msgstr "Нямецкая"
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
#: paperless/settings/__init__.py:569
|
||||
msgid "Greek"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:568
|
||||
#: paperless/settings/__init__.py:570
|
||||
msgid "English (GB)"
|
||||
msgstr "Англійская (Вялікабрытанія)"
|
||||
|
||||
#: paperless/settings/__init__.py:569
|
||||
#: paperless/settings/__init__.py:571
|
||||
msgid "Spanish"
|
||||
msgstr "Іспанская"
|
||||
|
||||
#: paperless/settings/__init__.py:570
|
||||
#: paperless/settings/__init__.py:572
|
||||
msgid "Persian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:571
|
||||
#: paperless/settings/__init__.py:573
|
||||
msgid "Finnish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:572
|
||||
#: paperless/settings/__init__.py:574
|
||||
msgid "French"
|
||||
msgstr "Французская"
|
||||
|
||||
#: paperless/settings/__init__.py:573
|
||||
#: paperless/settings/__init__.py:575
|
||||
msgid "Hungarian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:574
|
||||
#: paperless/settings/__init__.py:576
|
||||
msgid "Indonesian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:575
|
||||
#: paperless/settings/__init__.py:577
|
||||
msgid "Italian"
|
||||
msgstr "Італьянская"
|
||||
|
||||
#: paperless/settings/__init__.py:576
|
||||
#: paperless/settings/__init__.py:578
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:577
|
||||
#: paperless/settings/__init__.py:579
|
||||
msgid "Korean"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:578
|
||||
#: paperless/settings/__init__.py:580
|
||||
msgid "Luxembourgish"
|
||||
msgstr "Люксембургская"
|
||||
|
||||
#: paperless/settings/__init__.py:579
|
||||
#: paperless/settings/__init__.py:581
|
||||
msgid "Norwegian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:580
|
||||
#: paperless/settings/__init__.py:582
|
||||
msgid "Dutch"
|
||||
msgstr "Нідэрландская"
|
||||
|
||||
#: paperless/settings/__init__.py:581
|
||||
#: paperless/settings/__init__.py:583
|
||||
msgid "Polish"
|
||||
msgstr "Польская"
|
||||
|
||||
#: paperless/settings/__init__.py:582
|
||||
#: paperless/settings/__init__.py:584
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Партугальская (Бразілія)"
|
||||
|
||||
#: paperless/settings/__init__.py:583
|
||||
#: paperless/settings/__init__.py:585
|
||||
msgid "Portuguese"
|
||||
msgstr "Партугальская"
|
||||
|
||||
#: paperless/settings/__init__.py:584
|
||||
#: paperless/settings/__init__.py:586
|
||||
msgid "Romanian"
|
||||
msgstr "Румынская"
|
||||
|
||||
#: paperless/settings/__init__.py:585
|
||||
#: paperless/settings/__init__.py:587
|
||||
msgid "Russian"
|
||||
msgstr "Руская"
|
||||
|
||||
#: paperless/settings/__init__.py:586
|
||||
#: paperless/settings/__init__.py:588
|
||||
msgid "Slovak"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:587
|
||||
#: paperless/settings/__init__.py:589
|
||||
msgid "Slovenian"
|
||||
msgstr "Славенская"
|
||||
|
||||
#: paperless/settings/__init__.py:588
|
||||
#: paperless/settings/__init__.py:590
|
||||
msgid "Serbian"
|
||||
msgstr "Сербская"
|
||||
|
||||
#: paperless/settings/__init__.py:589
|
||||
#: paperless/settings/__init__.py:591
|
||||
msgid "Swedish"
|
||||
msgstr "Шведская"
|
||||
|
||||
#: paperless/settings/__init__.py:590
|
||||
#: paperless/settings/__init__.py:592
|
||||
msgid "Turkish"
|
||||
msgstr "Турэцкая"
|
||||
|
||||
#: paperless/settings/__init__.py:591
|
||||
#: paperless/settings/__init__.py:593
|
||||
msgid "Ukrainian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:592
|
||||
#: paperless/settings/__init__.py:594
|
||||
msgid "Vietnamese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:593
|
||||
#: paperless/settings/__init__.py:595
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "Кітайская спрошчаная"
|
||||
|
||||
#: paperless/settings/__init__.py:594
|
||||
#: paperless/settings/__init__.py:596
|
||||
msgid "Chinese Traditional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:435
|
||||
#: paperless/urls.py:438
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Адміністраванне Paperless-ngx"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-18 01:29+0000\n"
|
||||
"PO-Revision-Date: 2026-09-18 01:30\n"
|
||||
"POT-Creation-Date: 2026-10-02 23:09+0000\n"
|
||||
"PO-Revision-Date: 2026-10-02 23:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Bulgarian\n"
|
||||
"Language: bg_BG\n"
|
||||
@@ -1615,7 +1615,7 @@ msgid "workflow runs"
|
||||
msgstr "стартиране на работните процеси"
|
||||
|
||||
#: documents/serialisers.py:514 documents/serialisers.py:871
|
||||
#: documents/serialisers.py:2883 documents/views.py:343 documents/views.py:2726
|
||||
#: documents/serialisers.py:2895 documents/views.py:342 documents/views.py:2729
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1624,38 +1624,38 @@ msgstr ""
|
||||
msgid "Invalid color."
|
||||
msgstr "Невалиден цвят."
|
||||
|
||||
#: documents/serialisers.py:2350
|
||||
#: documents/serialisers.py:2362
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Файловия тип %(type)s не се поддържа"
|
||||
|
||||
#: documents/serialisers.py:2394
|
||||
#: documents/serialisers.py:2406
|
||||
#, python-format
|
||||
msgid "Custom field id must be an integer: %(id)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2401
|
||||
#: documents/serialisers.py:2413
|
||||
#, python-format
|
||||
msgid "Custom field with id %(id)s does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2418 documents/serialisers.py:2428
|
||||
#: documents/serialisers.py:2430 documents/serialisers.py:2440
|
||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2423
|
||||
#: documents/serialisers.py:2435
|
||||
msgid "Some custom fields don't exist or were specified twice."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2570
|
||||
#: documents/serialisers.py:2582
|
||||
msgid "Invalid variable detected."
|
||||
msgstr "Засечена е невалидна променлива."
|
||||
|
||||
#: documents/serialisers.py:2939
|
||||
#: documents/serialisers.py:2951
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2969 documents/views.py:4763
|
||||
#: documents/serialisers.py:2981 documents/views.py:4784
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1911,40 +1911,40 @@ msgstr ""
|
||||
msgid "Unable to parse URI {value}"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:336 documents/views.py:2723
|
||||
#: documents/views.py:335 documents/views.py:2726
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1670
|
||||
#: documents/views.py:1673
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1681
|
||||
#: documents/views.py:1684
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1693
|
||||
#: documents/views.py:1696
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:2548 documents/views.py:2864
|
||||
#: documents/views.py:2551 documents/views.py:2867
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4776
|
||||
#: documents/views.py:4797
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4822
|
||||
#: documents/views.py:4843
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4886
|
||||
#: documents/views.py:4907
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4900
|
||||
#: documents/views.py:4921
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -2189,194 +2189,198 @@ msgid "Sets the LLM embedding model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:369
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgid "Sets the LLM embedding API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:376
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:383
|
||||
msgid "Sets the LLM embedding chunk size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:382
|
||||
#: paperless/models.py:389
|
||||
msgid "Sets the LLM context size"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:388
|
||||
#: paperless/models.py:395
|
||||
msgid "Sets the LLM backend"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:396
|
||||
#: paperless/models.py:403
|
||||
msgid "Sets the LLM model"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:403
|
||||
#: paperless/models.py:410
|
||||
msgid "Sets the LLM API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:410
|
||||
#: paperless/models.py:417
|
||||
msgid "Sets the LLM endpoint, optional"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:417
|
||||
#: paperless/models.py:424
|
||||
msgid "Sets the LLM output language"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:424
|
||||
#: paperless/models.py:431
|
||||
msgid "Sets the LLM timeout in seconds"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:430
|
||||
#: paperless/models.py:437
|
||||
msgid "paperless application settings"
|
||||
msgstr "настройки на софтуера"
|
||||
|
||||
#: paperless/settings/__init__.py:558
|
||||
#: paperless/settings/__init__.py:560
|
||||
msgid "English (US)"
|
||||
msgstr "Английски (САЩ)"
|
||||
|
||||
#: paperless/settings/__init__.py:559
|
||||
#: paperless/settings/__init__.py:561
|
||||
msgid "Arabic"
|
||||
msgstr "Арабски"
|
||||
|
||||
#: paperless/settings/__init__.py:560
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "Afrikaans"
|
||||
msgstr "Африканс"
|
||||
|
||||
#: paperless/settings/__init__.py:561
|
||||
#: paperless/settings/__init__.py:563
|
||||
msgid "Belarusian"
|
||||
msgstr "Беларуски"
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Bulgarian"
|
||||
msgstr "Български"
|
||||
|
||||
#: paperless/settings/__init__.py:563
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Catalan"
|
||||
msgstr "Каталунски"
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
#: paperless/settings/__init__.py:566
|
||||
msgid "Czech"
|
||||
msgstr "Чешки"
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Danish"
|
||||
msgstr "Датски"
|
||||
|
||||
#: paperless/settings/__init__.py:566
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "German"
|
||||
msgstr "Немски"
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
#: paperless/settings/__init__.py:569
|
||||
msgid "Greek"
|
||||
msgstr "Гръцки"
|
||||
|
||||
#: paperless/settings/__init__.py:568
|
||||
#: paperless/settings/__init__.py:570
|
||||
msgid "English (GB)"
|
||||
msgstr "Английски (Великобритания)"
|
||||
|
||||
#: paperless/settings/__init__.py:569
|
||||
#: paperless/settings/__init__.py:571
|
||||
msgid "Spanish"
|
||||
msgstr "Испански"
|
||||
|
||||
#: paperless/settings/__init__.py:570
|
||||
#: paperless/settings/__init__.py:572
|
||||
msgid "Persian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:571
|
||||
#: paperless/settings/__init__.py:573
|
||||
msgid "Finnish"
|
||||
msgstr "Финландски"
|
||||
|
||||
#: paperless/settings/__init__.py:572
|
||||
#: paperless/settings/__init__.py:574
|
||||
msgid "French"
|
||||
msgstr "Френски"
|
||||
|
||||
#: paperless/settings/__init__.py:573
|
||||
#: paperless/settings/__init__.py:575
|
||||
msgid "Hungarian"
|
||||
msgstr "Унгарски"
|
||||
|
||||
#: paperless/settings/__init__.py:574
|
||||
#: paperless/settings/__init__.py:576
|
||||
msgid "Indonesian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:575
|
||||
#: paperless/settings/__init__.py:577
|
||||
msgid "Italian"
|
||||
msgstr "Италиански"
|
||||
|
||||
#: paperless/settings/__init__.py:576
|
||||
#: paperless/settings/__init__.py:578
|
||||
msgid "Japanese"
|
||||
msgstr "Японски"
|
||||
|
||||
#: paperless/settings/__init__.py:577
|
||||
#: paperless/settings/__init__.py:579
|
||||
msgid "Korean"
|
||||
msgstr "Корейски"
|
||||
|
||||
#: paperless/settings/__init__.py:578
|
||||
#: paperless/settings/__init__.py:580
|
||||
msgid "Luxembourgish"
|
||||
msgstr "Люксембургски"
|
||||
|
||||
#: paperless/settings/__init__.py:579
|
||||
#: paperless/settings/__init__.py:581
|
||||
msgid "Norwegian"
|
||||
msgstr "Норвежки"
|
||||
|
||||
#: paperless/settings/__init__.py:580
|
||||
#: paperless/settings/__init__.py:582
|
||||
msgid "Dutch"
|
||||
msgstr "Холандски"
|
||||
|
||||
#: paperless/settings/__init__.py:581
|
||||
#: paperless/settings/__init__.py:583
|
||||
msgid "Polish"
|
||||
msgstr "Полски"
|
||||
|
||||
#: paperless/settings/__init__.py:582
|
||||
#: paperless/settings/__init__.py:584
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Португалски (Бразилия)"
|
||||
|
||||
#: paperless/settings/__init__.py:583
|
||||
#: paperless/settings/__init__.py:585
|
||||
msgid "Portuguese"
|
||||
msgstr "Португалски"
|
||||
|
||||
#: paperless/settings/__init__.py:584
|
||||
#: paperless/settings/__init__.py:586
|
||||
msgid "Romanian"
|
||||
msgstr "Румънски"
|
||||
|
||||
#: paperless/settings/__init__.py:585
|
||||
#: paperless/settings/__init__.py:587
|
||||
msgid "Russian"
|
||||
msgstr "Руски"
|
||||
|
||||
#: paperless/settings/__init__.py:586
|
||||
#: paperless/settings/__init__.py:588
|
||||
msgid "Slovak"
|
||||
msgstr "Словашки"
|
||||
|
||||
#: paperless/settings/__init__.py:587
|
||||
#: paperless/settings/__init__.py:589
|
||||
msgid "Slovenian"
|
||||
msgstr "Словенски"
|
||||
|
||||
#: paperless/settings/__init__.py:588
|
||||
#: paperless/settings/__init__.py:590
|
||||
msgid "Serbian"
|
||||
msgstr "Сръбски"
|
||||
|
||||
#: paperless/settings/__init__.py:589
|
||||
#: paperless/settings/__init__.py:591
|
||||
msgid "Swedish"
|
||||
msgstr "Шведски"
|
||||
|
||||
#: paperless/settings/__init__.py:590
|
||||
#: paperless/settings/__init__.py:592
|
||||
msgid "Turkish"
|
||||
msgstr "Турски"
|
||||
|
||||
#: paperless/settings/__init__.py:591
|
||||
#: paperless/settings/__init__.py:593
|
||||
msgid "Ukrainian"
|
||||
msgstr "Украински"
|
||||
|
||||
#: paperless/settings/__init__.py:592
|
||||
#: paperless/settings/__init__.py:594
|
||||
msgid "Vietnamese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:593
|
||||
#: paperless/settings/__init__.py:595
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "Китайски опростен"
|
||||
|
||||
#: paperless/settings/__init__.py:594
|
||||
#: paperless/settings/__init__.py:596
|
||||
msgid "Chinese Traditional"
|
||||
msgstr "Китайски традиционен"
|
||||
|
||||
#: paperless/urls.py:435
|
||||
#: paperless/urls.py:438
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Paperless-ngx администрация"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-18 01:29+0000\n"
|
||||
"PO-Revision-Date: 2026-09-18 01:30\n"
|
||||
"POT-Creation-Date: 2026-10-02 23:09+0000\n"
|
||||
"PO-Revision-Date: 2026-10-02 23:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Catalan\n"
|
||||
"Language: ca_ES\n"
|
||||
@@ -1615,7 +1615,7 @@ msgid "workflow runs"
|
||||
msgstr "flux corrents"
|
||||
|
||||
#: documents/serialisers.py:514 documents/serialisers.py:871
|
||||
#: documents/serialisers.py:2883 documents/views.py:343 documents/views.py:2726
|
||||
#: documents/serialisers.py:2895 documents/views.py:342 documents/views.py:2729
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Permisos insuficients."
|
||||
@@ -1624,38 +1624,38 @@ msgstr "Permisos insuficients."
|
||||
msgid "Invalid color."
|
||||
msgstr "Color Invàlid."
|
||||
|
||||
#: documents/serialisers.py:2350
|
||||
#: documents/serialisers.py:2362
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Tipus arxiu %(type)s no suportat"
|
||||
|
||||
#: documents/serialisers.py:2394
|
||||
#: documents/serialisers.py:2406
|
||||
#, python-format
|
||||
msgid "Custom field id must be an integer: %(id)s"
|
||||
msgstr "ID de camp personalizat ha de ser enter: %(id)s"
|
||||
|
||||
#: documents/serialisers.py:2401
|
||||
#: documents/serialisers.py:2413
|
||||
#, python-format
|
||||
msgid "Custom field with id %(id)s does not exist"
|
||||
msgstr "Camp personalitzat amb ID %(id)s no existeix"
|
||||
|
||||
#: documents/serialisers.py:2418 documents/serialisers.py:2428
|
||||
#: documents/serialisers.py:2430 documents/serialisers.py:2440
|
||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||
msgstr "Camps personalitzats han de ser una llista d'enters o un objecte que mapegi els identificadors amb els valors."
|
||||
|
||||
#: documents/serialisers.py:2423
|
||||
#: documents/serialisers.py:2435
|
||||
msgid "Some custom fields don't exist or were specified twice."
|
||||
msgstr "Alguns camps personalitzats no existeixen o s'han especificat dues vegades."
|
||||
|
||||
#: documents/serialisers.py:2570
|
||||
#: documents/serialisers.py:2582
|
||||
msgid "Invalid variable detected."
|
||||
msgstr "Variable detectada invàlida."
|
||||
|
||||
#: documents/serialisers.py:2939
|
||||
#: documents/serialisers.py:2951
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Duplicat d'identificadors de documents no permès."
|
||||
|
||||
#: documents/serialisers.py:2969 documents/views.py:4763
|
||||
#: documents/serialisers.py:2981 documents/views.py:4784
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Documents no trobats: %(ids)s"
|
||||
@@ -1911,40 +1911,40 @@ msgstr "L'esquema d'URI '{parts.scheme}' no està permès. Esquemes permesos: {'
|
||||
msgid "Unable to parse URI {value}"
|
||||
msgstr "No s'ha pogut analitzar l'URI {value}"
|
||||
|
||||
#: documents/views.py:336 documents/views.py:2723
|
||||
#: documents/views.py:335 documents/views.py:2726
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Invalid more_like_id"
|
||||
|
||||
#: documents/views.py:1670
|
||||
#: documents/views.py:1673
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Configuració AI invàlida."
|
||||
|
||||
#: documents/views.py:1681
|
||||
#: documents/views.py:1684
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1693
|
||||
#: documents/views.py:1696
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:2548 documents/views.py:2864
|
||||
#: documents/views.py:2551 documents/views.py:2867
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Especifica només un dels següents valors: text, title_search, query o more_like_id."
|
||||
|
||||
#: documents/views.py:4776
|
||||
#: documents/views.py:4797
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Permisos insuficients per compartir document %(id)s."
|
||||
|
||||
#: documents/views.py:4822
|
||||
#: documents/views.py:4843
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paquet ja s'està processant."
|
||||
|
||||
#: documents/views.py:4886
|
||||
#: documents/views.py:4907
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "El paquet de link encarà s'està preparant. Prova de nou més tard."
|
||||
|
||||
#: documents/views.py:4900
|
||||
#: documents/views.py:4921
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "El paquet d'enllaç no està disponible."
|
||||
|
||||
@@ -2189,194 +2189,198 @@ msgid "Sets the LLM embedding model"
|
||||
msgstr "Defineix model d'incrustació LLM"
|
||||
|
||||
#: paperless/models.py:369
|
||||
msgid "Sets the LLM embedding API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:376
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgstr "Defineix el punt final d'incrustació de LLM, opcional"
|
||||
|
||||
#: paperless/models.py:376
|
||||
#: paperless/models.py:383
|
||||
msgid "Sets the LLM embedding chunk size"
|
||||
msgstr "Defineix la mida del fragment d'incrustació de LLM"
|
||||
|
||||
#: paperless/models.py:382
|
||||
#: paperless/models.py:389
|
||||
msgid "Sets the LLM context size"
|
||||
msgstr "Defineix la mida del context LLM"
|
||||
|
||||
#: paperless/models.py:388
|
||||
#: paperless/models.py:395
|
||||
msgid "Sets the LLM backend"
|
||||
msgstr "Estableix backend LLM"
|
||||
|
||||
#: paperless/models.py:396
|
||||
#: paperless/models.py:403
|
||||
msgid "Sets the LLM model"
|
||||
msgstr "Estableix model LLM"
|
||||
|
||||
#: paperless/models.py:403
|
||||
#: paperless/models.py:410
|
||||
msgid "Sets the LLM API key"
|
||||
msgstr "Estableix clau API de LLM"
|
||||
|
||||
#: paperless/models.py:410
|
||||
#: paperless/models.py:417
|
||||
msgid "Sets the LLM endpoint, optional"
|
||||
msgstr "Estableix el punt final de l'LLM, opcional"
|
||||
|
||||
#: paperless/models.py:417
|
||||
#: paperless/models.py:424
|
||||
msgid "Sets the LLM output language"
|
||||
msgstr "Estableix l'idioma de sortida de LLM"
|
||||
|
||||
#: paperless/models.py:424
|
||||
#: paperless/models.py:431
|
||||
msgid "Sets the LLM timeout in seconds"
|
||||
msgstr "Estableix temps d'espera en segons"
|
||||
|
||||
#: paperless/models.py:430
|
||||
#: paperless/models.py:437
|
||||
msgid "paperless application settings"
|
||||
msgstr "configuració de l'aplicació paperless"
|
||||
|
||||
#: paperless/settings/__init__.py:558
|
||||
#: paperless/settings/__init__.py:560
|
||||
msgid "English (US)"
|
||||
msgstr "Anglès (US)"
|
||||
|
||||
#: paperless/settings/__init__.py:559
|
||||
#: paperless/settings/__init__.py:561
|
||||
msgid "Arabic"
|
||||
msgstr "Àrab"
|
||||
|
||||
#: paperless/settings/__init__.py:560
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "Afrikaans"
|
||||
msgstr "Africà"
|
||||
|
||||
#: paperless/settings/__init__.py:561
|
||||
#: paperless/settings/__init__.py:563
|
||||
msgid "Belarusian"
|
||||
msgstr "Bielorús"
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Bulgarian"
|
||||
msgstr "Búlgar"
|
||||
|
||||
#: paperless/settings/__init__.py:563
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Catalan"
|
||||
msgstr "Català"
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
#: paperless/settings/__init__.py:566
|
||||
msgid "Czech"
|
||||
msgstr "Txec"
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Danish"
|
||||
msgstr "Danès"
|
||||
|
||||
#: paperless/settings/__init__.py:566
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "German"
|
||||
msgstr "Alemany"
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
#: paperless/settings/__init__.py:569
|
||||
msgid "Greek"
|
||||
msgstr "Grec"
|
||||
|
||||
#: paperless/settings/__init__.py:568
|
||||
#: paperless/settings/__init__.py:570
|
||||
msgid "English (GB)"
|
||||
msgstr "Anglès (GB)"
|
||||
|
||||
#: paperless/settings/__init__.py:569
|
||||
#: paperless/settings/__init__.py:571
|
||||
msgid "Spanish"
|
||||
msgstr "Espanyol"
|
||||
|
||||
#: paperless/settings/__init__.py:570
|
||||
#: paperless/settings/__init__.py:572
|
||||
msgid "Persian"
|
||||
msgstr "Persa"
|
||||
|
||||
#: paperless/settings/__init__.py:571
|
||||
#: paperless/settings/__init__.py:573
|
||||
msgid "Finnish"
|
||||
msgstr "Finès"
|
||||
|
||||
#: paperless/settings/__init__.py:572
|
||||
#: paperless/settings/__init__.py:574
|
||||
msgid "French"
|
||||
msgstr "Francès"
|
||||
|
||||
#: paperless/settings/__init__.py:573
|
||||
#: paperless/settings/__init__.py:575
|
||||
msgid "Hungarian"
|
||||
msgstr "Hongarès"
|
||||
|
||||
#: paperless/settings/__init__.py:574
|
||||
#: paperless/settings/__init__.py:576
|
||||
msgid "Indonesian"
|
||||
msgstr "Indonesi"
|
||||
|
||||
#: paperless/settings/__init__.py:575
|
||||
#: paperless/settings/__init__.py:577
|
||||
msgid "Italian"
|
||||
msgstr "Italià"
|
||||
|
||||
#: paperless/settings/__init__.py:576
|
||||
#: paperless/settings/__init__.py:578
|
||||
msgid "Japanese"
|
||||
msgstr "Japonès"
|
||||
|
||||
#: paperless/settings/__init__.py:577
|
||||
#: paperless/settings/__init__.py:579
|
||||
msgid "Korean"
|
||||
msgstr "Coreà"
|
||||
|
||||
#: paperless/settings/__init__.py:578
|
||||
#: paperless/settings/__init__.py:580
|
||||
msgid "Luxembourgish"
|
||||
msgstr "Luxemburguès"
|
||||
|
||||
#: paperless/settings/__init__.py:579
|
||||
#: paperless/settings/__init__.py:581
|
||||
msgid "Norwegian"
|
||||
msgstr "Noruec"
|
||||
|
||||
#: paperless/settings/__init__.py:580
|
||||
#: paperless/settings/__init__.py:582
|
||||
msgid "Dutch"
|
||||
msgstr "Holandès"
|
||||
|
||||
#: paperless/settings/__init__.py:581
|
||||
#: paperless/settings/__init__.py:583
|
||||
msgid "Polish"
|
||||
msgstr "Polonès"
|
||||
|
||||
#: paperless/settings/__init__.py:582
|
||||
#: paperless/settings/__init__.py:584
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portuguès (Brasil)"
|
||||
|
||||
#: paperless/settings/__init__.py:583
|
||||
#: paperless/settings/__init__.py:585
|
||||
msgid "Portuguese"
|
||||
msgstr "Portuguès"
|
||||
|
||||
#: paperless/settings/__init__.py:584
|
||||
#: paperless/settings/__init__.py:586
|
||||
msgid "Romanian"
|
||||
msgstr "Romanès"
|
||||
|
||||
#: paperless/settings/__init__.py:585
|
||||
#: paperless/settings/__init__.py:587
|
||||
msgid "Russian"
|
||||
msgstr "Rus"
|
||||
|
||||
#: paperless/settings/__init__.py:586
|
||||
#: paperless/settings/__init__.py:588
|
||||
msgid "Slovak"
|
||||
msgstr "Eslovac"
|
||||
|
||||
#: paperless/settings/__init__.py:587
|
||||
#: paperless/settings/__init__.py:589
|
||||
msgid "Slovenian"
|
||||
msgstr "Eslovè"
|
||||
|
||||
#: paperless/settings/__init__.py:588
|
||||
#: paperless/settings/__init__.py:590
|
||||
msgid "Serbian"
|
||||
msgstr "Serbi"
|
||||
|
||||
#: paperless/settings/__init__.py:589
|
||||
#: paperless/settings/__init__.py:591
|
||||
msgid "Swedish"
|
||||
msgstr "Suec"
|
||||
|
||||
#: paperless/settings/__init__.py:590
|
||||
#: paperless/settings/__init__.py:592
|
||||
msgid "Turkish"
|
||||
msgstr "Turc"
|
||||
|
||||
#: paperless/settings/__init__.py:591
|
||||
#: paperless/settings/__init__.py:593
|
||||
msgid "Ukrainian"
|
||||
msgstr "Ucranià"
|
||||
|
||||
#: paperless/settings/__init__.py:592
|
||||
#: paperless/settings/__init__.py:594
|
||||
msgid "Vietnamese"
|
||||
msgstr "Vietnamita"
|
||||
|
||||
#: paperless/settings/__init__.py:593
|
||||
#: paperless/settings/__init__.py:595
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "Xinès Simplificat"
|
||||
|
||||
#: paperless/settings/__init__.py:594
|
||||
#: paperless/settings/__init__.py:596
|
||||
msgid "Chinese Traditional"
|
||||
msgstr "Xinès tradicional"
|
||||
|
||||
#: paperless/urls.py:435
|
||||
#: paperless/urls.py:438
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Administració Paperless-ngx"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-18 01:29+0000\n"
|
||||
"PO-Revision-Date: 2026-09-18 01:30\n"
|
||||
"POT-Creation-Date: 2026-10-02 23:09+0000\n"
|
||||
"PO-Revision-Date: 2026-10-02 23:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Czech\n"
|
||||
"Language: cs_CZ\n"
|
||||
@@ -1615,7 +1615,7 @@ msgid "workflow runs"
|
||||
msgstr "spuštění pracovních postupů"
|
||||
|
||||
#: documents/serialisers.py:514 documents/serialisers.py:871
|
||||
#: documents/serialisers.py:2883 documents/views.py:343 documents/views.py:2726
|
||||
#: documents/serialisers.py:2895 documents/views.py:342 documents/views.py:2729
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Nedostatečná oprávnění."
|
||||
@@ -1624,38 +1624,38 @@ msgstr "Nedostatečná oprávnění."
|
||||
msgid "Invalid color."
|
||||
msgstr "Neplatná barva."
|
||||
|
||||
#: documents/serialisers.py:2350
|
||||
#: documents/serialisers.py:2362
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Typ souboru %(type)s není podporován"
|
||||
|
||||
#: documents/serialisers.py:2394
|
||||
#: documents/serialisers.py:2406
|
||||
#, python-format
|
||||
msgid "Custom field id must be an integer: %(id)s"
|
||||
msgstr "Vlastní ID pole musí být celé číslo: %(id)s"
|
||||
|
||||
#: documents/serialisers.py:2401
|
||||
#: documents/serialisers.py:2413
|
||||
#, python-format
|
||||
msgid "Custom field with id %(id)s does not exist"
|
||||
msgstr "Vlastní pole s ID %(id)s neexistuje"
|
||||
|
||||
#: documents/serialisers.py:2418 documents/serialisers.py:2428
|
||||
#: documents/serialisers.py:2430 documents/serialisers.py:2440
|
||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||
msgstr "Vlastní pole musí být seznam celých čísel nebo ID pro mapování objektů na hodnoty."
|
||||
|
||||
#: documents/serialisers.py:2423
|
||||
#: documents/serialisers.py:2435
|
||||
msgid "Some custom fields don't exist or were specified twice."
|
||||
msgstr "Některá vlastní pole neexistují nebo byla zadána dvakrát."
|
||||
|
||||
#: documents/serialisers.py:2570
|
||||
#: documents/serialisers.py:2582
|
||||
msgid "Invalid variable detected."
|
||||
msgstr "Zjištěna neplatná proměnná."
|
||||
|
||||
#: documents/serialisers.py:2939
|
||||
#: documents/serialisers.py:2951
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2969 documents/views.py:4763
|
||||
#: documents/serialisers.py:2981 documents/views.py:4784
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1912,40 +1912,40 @@ msgstr "URI schéma '{parts.scheme}' není povoleno. Povolená schémata: {',\n"
|
||||
msgid "Unable to parse URI {value}"
|
||||
msgstr "Nelze zpracovat URI {value}"
|
||||
|
||||
#: documents/views.py:336 documents/views.py:2723
|
||||
#: documents/views.py:335 documents/views.py:2726
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1670
|
||||
#: documents/views.py:1673
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1681
|
||||
#: documents/views.py:1684
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1693
|
||||
#: documents/views.py:1696
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:2548 documents/views.py:2864
|
||||
#: documents/views.py:2551 documents/views.py:2867
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4776
|
||||
#: documents/views.py:4797
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Nedostatečná oprávnění ke sdílení dokumentu %(id)s."
|
||||
|
||||
#: documents/views.py:4822
|
||||
#: documents/views.py:4843
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4886
|
||||
#: documents/views.py:4907
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4900
|
||||
#: documents/views.py:4921
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -2190,194 +2190,198 @@ msgid "Sets the LLM embedding model"
|
||||
msgstr "Nastaví model vkládání LLM"
|
||||
|
||||
#: paperless/models.py:369
|
||||
msgid "Sets the LLM embedding API key"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:376
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgstr "Nastaví koncový bod vkládání LLM, volitelné"
|
||||
|
||||
#: paperless/models.py:376
|
||||
#: paperless/models.py:383
|
||||
msgid "Sets the LLM embedding chunk size"
|
||||
msgstr "Nastaví velikost bloků vkládání LLM"
|
||||
|
||||
#: paperless/models.py:382
|
||||
#: paperless/models.py:389
|
||||
msgid "Sets the LLM context size"
|
||||
msgstr "Nastaví velikost kontextu LLM"
|
||||
|
||||
#: paperless/models.py:388
|
||||
#: paperless/models.py:395
|
||||
msgid "Sets the LLM backend"
|
||||
msgstr "Nastaví backend LLM"
|
||||
|
||||
#: paperless/models.py:396
|
||||
#: paperless/models.py:403
|
||||
msgid "Sets the LLM model"
|
||||
msgstr "Nastaví model LLM"
|
||||
|
||||
#: paperless/models.py:403
|
||||
#: paperless/models.py:410
|
||||
msgid "Sets the LLM API key"
|
||||
msgstr "Nastaví klíč API LLM"
|
||||
|
||||
#: paperless/models.py:410
|
||||
#: paperless/models.py:417
|
||||
msgid "Sets the LLM endpoint, optional"
|
||||
msgstr "Nastaví koncový bod LLM, volitelné"
|
||||
|
||||
#: paperless/models.py:417
|
||||
#: paperless/models.py:424
|
||||
msgid "Sets the LLM output language"
|
||||
msgstr "Nastaví výstupní jazyk LLM"
|
||||
|
||||
#: paperless/models.py:424
|
||||
#: paperless/models.py:431
|
||||
msgid "Sets the LLM timeout in seconds"
|
||||
msgstr "Nastaví časový limit LLM v sekundách"
|
||||
|
||||
#: paperless/models.py:430
|
||||
#: paperless/models.py:437
|
||||
msgid "paperless application settings"
|
||||
msgstr "nastavení aplikace paperless"
|
||||
|
||||
#: paperless/settings/__init__.py:558
|
||||
#: paperless/settings/__init__.py:560
|
||||
msgid "English (US)"
|
||||
msgstr "Angličtina (US)"
|
||||
|
||||
#: paperless/settings/__init__.py:559
|
||||
#: paperless/settings/__init__.py:561
|
||||
msgid "Arabic"
|
||||
msgstr "Arabština"
|
||||
|
||||
#: paperless/settings/__init__.py:560
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "Afrikaans"
|
||||
msgstr "Afrikánština"
|
||||
|
||||
#: paperless/settings/__init__.py:561
|
||||
#: paperless/settings/__init__.py:563
|
||||
msgid "Belarusian"
|
||||
msgstr "Běloruština"
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Bulgarian"
|
||||
msgstr "Bulharština"
|
||||
|
||||
#: paperless/settings/__init__.py:563
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Catalan"
|
||||
msgstr "Katalánština"
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
#: paperless/settings/__init__.py:566
|
||||
msgid "Czech"
|
||||
msgstr "Čeština"
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Danish"
|
||||
msgstr "Dánština"
|
||||
|
||||
#: paperless/settings/__init__.py:566
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "German"
|
||||
msgstr "Němčina"
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
#: paperless/settings/__init__.py:569
|
||||
msgid "Greek"
|
||||
msgstr "Řečtina"
|
||||
|
||||
#: paperless/settings/__init__.py:568
|
||||
#: paperless/settings/__init__.py:570
|
||||
msgid "English (GB)"
|
||||
msgstr "Angličtina (GB)"
|
||||
|
||||
#: paperless/settings/__init__.py:569
|
||||
#: paperless/settings/__init__.py:571
|
||||
msgid "Spanish"
|
||||
msgstr "Španělština"
|
||||
|
||||
#: paperless/settings/__init__.py:570
|
||||
#: paperless/settings/__init__.py:572
|
||||
msgid "Persian"
|
||||
msgstr "Perština"
|
||||
|
||||
#: paperless/settings/__init__.py:571
|
||||
#: paperless/settings/__init__.py:573
|
||||
msgid "Finnish"
|
||||
msgstr "Finština"
|
||||
|
||||
#: paperless/settings/__init__.py:572
|
||||
#: paperless/settings/__init__.py:574
|
||||
msgid "French"
|
||||
msgstr "Francouzština"
|
||||
|
||||
#: paperless/settings/__init__.py:573
|
||||
#: paperless/settings/__init__.py:575
|
||||
msgid "Hungarian"
|
||||
msgstr "Maďarština"
|
||||
|
||||
#: paperless/settings/__init__.py:574
|
||||
#: paperless/settings/__init__.py:576
|
||||
msgid "Indonesian"
|
||||
msgstr "Indonéština"
|
||||
|
||||
#: paperless/settings/__init__.py:575
|
||||
#: paperless/settings/__init__.py:577
|
||||
msgid "Italian"
|
||||
msgstr "Italština"
|
||||
|
||||
#: paperless/settings/__init__.py:576
|
||||
#: paperless/settings/__init__.py:578
|
||||
msgid "Japanese"
|
||||
msgstr "Japonština"
|
||||
|
||||
#: paperless/settings/__init__.py:577
|
||||
#: paperless/settings/__init__.py:579
|
||||
msgid "Korean"
|
||||
msgstr "Korejština"
|
||||
|
||||
#: paperless/settings/__init__.py:578
|
||||
#: paperless/settings/__init__.py:580
|
||||
msgid "Luxembourgish"
|
||||
msgstr "Lucemburština"
|
||||
|
||||
#: paperless/settings/__init__.py:579
|
||||
#: paperless/settings/__init__.py:581
|
||||
msgid "Norwegian"
|
||||
msgstr "Norština"
|
||||
|
||||
#: paperless/settings/__init__.py:580
|
||||
#: paperless/settings/__init__.py:582
|
||||
msgid "Dutch"
|
||||
msgstr "Holandština"
|
||||
|
||||
#: paperless/settings/__init__.py:581
|
||||
#: paperless/settings/__init__.py:583
|
||||
msgid "Polish"
|
||||
msgstr "Polština"
|
||||
|
||||
#: paperless/settings/__init__.py:582
|
||||
#: paperless/settings/__init__.py:584
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugalština (Brazílie)"
|
||||
|
||||
#: paperless/settings/__init__.py:583
|
||||
#: paperless/settings/__init__.py:585
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugalština"
|
||||
|
||||
#: paperless/settings/__init__.py:584
|
||||
#: paperless/settings/__init__.py:586
|
||||
msgid "Romanian"
|
||||
msgstr "Rumunština"
|
||||
|
||||
#: paperless/settings/__init__.py:585
|
||||
#: paperless/settings/__init__.py:587
|
||||
msgid "Russian"
|
||||
msgstr "Ruština"
|
||||
|
||||
#: paperless/settings/__init__.py:586
|
||||
#: paperless/settings/__init__.py:588
|
||||
msgid "Slovak"
|
||||
msgstr "Slovenština"
|
||||
|
||||
#: paperless/settings/__init__.py:587
|
||||
#: paperless/settings/__init__.py:589
|
||||
msgid "Slovenian"
|
||||
msgstr "Slovinština"
|
||||
|
||||
#: paperless/settings/__init__.py:588
|
||||
#: paperless/settings/__init__.py:590
|
||||
msgid "Serbian"
|
||||
msgstr "Srbština"
|
||||
|
||||
#: paperless/settings/__init__.py:589
|
||||
#: paperless/settings/__init__.py:591
|
||||
msgid "Swedish"
|
||||
msgstr "Švédština"
|
||||
|
||||
#: paperless/settings/__init__.py:590
|
||||
#: paperless/settings/__init__.py:592
|
||||
msgid "Turkish"
|
||||
msgstr "Turečtina"
|
||||
|
||||
#: paperless/settings/__init__.py:591
|
||||
#: paperless/settings/__init__.py:593
|
||||
msgid "Ukrainian"
|
||||
msgstr "Ukrajinština"
|
||||
|
||||
#: paperless/settings/__init__.py:592
|
||||
#: paperless/settings/__init__.py:594
|
||||
msgid "Vietnamese"
|
||||
msgstr "Vietnamština"
|
||||
|
||||
#: paperless/settings/__init__.py:593
|
||||
#: paperless/settings/__init__.py:595
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "Čínština (zjednodušená)"
|
||||
|
||||
#: paperless/settings/__init__.py:594
|
||||
#: paperless/settings/__init__.py:596
|
||||
msgid "Chinese Traditional"
|
||||
msgstr "Čínština (tradiční)"
|
||||
|
||||
#: paperless/urls.py:435
|
||||
#: paperless/urls.py:438
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Administrace Paperless-ngx"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user