Compare commits

..
Author SHA1 Message Date
stumpylog 507889f06a Updates to ruff 0.16 and ignores, autofixes or handles all the new rules 2026-07-24 11:02:11 -07:00
stumpylog 4971be9b37 Updates the script in docker too 2026-07-24 10:41:18 -07:00
stumpylogandClaude Sonnet 4.6 219cda569c ruff: enable S324 (hashlib insecure hash functions)
Adds usedforsecurity=False to all hashlib.md5() calls, documenting
that these are used for file checksum comparison, not security.
The production call in _path_matches_checksum will be replaced with
compute_checksum() (SHA-256) in a separate branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 10:41:17 -07:00
stumpylogandClaude Sonnet 4.6 de227d0f71 ruff: enable PERF (perflint)
Fixes 9 violations — loop-based append replaced with comprehensions
or extend throughout production and test code:
- PERF401: list comprehensions / extend for transformed lists
- PERF402: list() around a generator for copied lists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 10:40:35 -07:00
stumpylog 5cdc82ae35 ruff: enable DTZ (flake8-datetimez)
Fixes 44 violations — naive datetime usage replaced with tz-aware
equivalents throughout production and test code:
- datetime.now() → timezone.now() (Django) or datetime.now(tz=UTC)
- datetime.fromtimestamp() → datetime.fromtimestamp(ts, tz=UTC)
- datetime.date.today() → timezone.now().date()
- datetime.datetime(...) constructors → tzinfo=UTC in tests
- UP017 auto-converted datetime.timezone.utc → datetime.UTC (py3.11+)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

# Conflicts:
#	src/documents/views.py
#	src/paperless_mail/mail.py
2026-07-24 10:39:44 -07:00
stumpylogandClaude Sonnet 4.6 976eb3beaa ruff: enable B (flake8-bugbear)
Fixes 71 violations across production and test code:
- B904 (~50): raise-from in except blocks; from None at API/view
  boundaries, from exc where the cause is the direct origin
- B017 (9): pytest.raises(Exception) → specific type or match= arg
- B007 (5): unused loop vars renamed to _
- B027 (1): missing @abstractmethod on DateParserPluginBase.__exit__
- B028 (3): warnings.warn without stacklevel=2 in test utils
- B011 (1): assert False → raise AssertionError()
- B905 (3): zip() without strict=False
- B009 (3): getattr with constant string (auto-fixed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 10:37:52 -07:00
stumpylogandClaude Sonnet 4.6 6f1673e0ae ruff: enable G (logging format), ignore G004 (f-strings)
Replaces the single G201 selector with the full G group.
Fixes 2x G003 (string concat in log calls) and 2x G202 (redundant
exc_info on logger.exception). G004 (f-strings in logging) is ignored
as f-string style is accepted throughout this codebase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-24 10:36:59 -07:00
150 changed files with 2500 additions and 3872 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ repos:
- 'prettier-plugin-organize-imports@4.3.0'
# Python hooks
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.20
rev: v0.16.0
hooks:
- id: ruff-check
- id: ruff-format
+1 -1
View File
@@ -61,7 +61,7 @@ def replace_with_symlinks(
total_duplicates = 0
space_saved = 0
for file_hash, file_list in duplicate_groups.items():
for file_list in duplicate_groups.values():
# Keep the first file as the original, replace others with symlinks
original_file = file_list[0]
duplicates = file_list[1:]
-17
View File
@@ -151,23 +151,6 @@ the background.
### Bare Metal Route {#bare-metal-updating}
!!! warning
Extracting a new release archive on top of an existing installation
(e.g. `tar -xf paperless-ngx-vX.Y.Z.tar.xz -C /opt/paperless`) only adds
and overwrites files -- it does not remove files that were deleted in
the new release. Leftover files from an old version, such as
superseded database migrations, can remain on disk and cause errors
like `NodeNotFoundError` during `manage.py migrate`.
Before unpacking a new release over an existing bare-metal
installation, remove the previous source tree first (everything
except your `media`, `data`, and `consume` directories and your
`paperless.conf`/`.env`), or unpack into a fresh directory and move
your persistent data and configuration over. Installations updated via
`git pull` on a clean checkout are not affected, since Git removes
files that no longer exist upstream.
After grabbing the new release and unpacking the contents, do the
following:
-59
View File
@@ -1,64 +1,5 @@
# Changelog
## paperless-ngx 3.0.2
### Bug Fixes
- Fix: fix broken migration in 3.0.1 [@shamoon](https://github.com/shamoon) ([#13242](https://github.com/paperless-ngx/paperless-ngx/pull/13242))
### All App Changes
- Fix: fix broken migration in 3.0.1 [@shamoon](https://github.com/shamoon) ([#13242](https://github.com/paperless-ngx/paperless-ngx/pull/13242))
## paperless-ngx 3.0.1
### Bug Fixes
- Fix: selection\_data re-derives the filtered document set 5 times over [@stumpylog](https://github.com/stumpylog) ([#13229](https://github.com/paperless-ngx/paperless-ngx/pull/13229))
- Fix: exclude source document from its own RAG similarity results [@stumpylog](https://github.com/stumpylog) ([#13233](https://github.com/paperless-ngx/paperless-ngx/pull/13233))
- Fix: split mailrule maximum\_age clamp into its own migration [@stumpylog](https://github.com/stumpylog) ([#13231](https://github.com/paperless-ngx/paperless-ngx/pull/13231))
- Chore: mark yes in confirm button for translation [@shamoon](https://github.com/shamoon) ([#13225](https://github.com/paperless-ngx/paperless-ngx/pull/13225))
- Fix: correct URL for W001 check [@shamoon](https://github.com/shamoon) ([#13220](https://github.com/paperless-ngx/paperless-ngx/pull/13220))
- Fix: ensure create dialog uses correct attribute type [@shamoon](https://github.com/shamoon) ([#13221](https://github.com/paperless-ngx/paperless-ngx/pull/13221))
- Fix: correct database engine from postgres to postgresql [@OberstVonGatow](https://github.com/OberstVonGatow) ([#13213](https://github.com/paperless-ngx/paperless-ngx/pull/13213))
- Fix: remove unnecessary .distinct() dominating document list queries [@stumpylog](https://github.com/stumpylog) ([#13205](https://github.com/paperless-ngx/paperless-ngx/pull/13205))
- Fix: also wrap non-breaking words in chat messages, handle whitespace [@shamoon](https://github.com/shamoon) ([#13211](https://github.com/paperless-ngx/paperless-ngx/pull/13211))
- Fix: tag/custom-field document\_count scales badly with tag count [@stumpylog](https://github.com/stumpylog) ([#13203](https://github.com/paperless-ngx/paperless-ngx/pull/13203))
- Fix: batch document user\_can\_change checks to avoid per-row N+1 [@stumpylog](https://github.com/stumpylog) ([#13204](https://github.com/paperless-ngx/paperless-ngx/pull/13204))
- Fix: fix app title restoration [@shamoon](https://github.com/shamoon) ([#13208](https://github.com/paperless-ngx/paperless-ngx/pull/13208))
- Fix: handle long wrapping titles in AI chat document list [@shamoon](https://github.com/shamoon) ([#13206](https://github.com/paperless-ngx/paperless-ngx/pull/13206))
### Dependencies
- Chore(deps-dev): Bump postcss from 8.5.6 to 8.5.22 in /src/paperless\_mail/templates in the npm\_and\_yarn group across 1 directory @[dependabot[bot]](https://github.com/apps/dependabot) ([#13236](https://github.com/paperless-ngx/paperless-ngx/pull/13236))
- Chore(deps): Bump pyasn1 from 0.6.3 to 0.6.4 in the uv group across 1 directory @[dependabot[bot]](https://github.com/apps/dependabot) ([#13237](https://github.com/paperless-ngx/paperless-ngx/pull/13237))
- Chore(deps): Bump the uv group across 1 directory with 8 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#13189](https://github.com/paperless-ngx/paperless-ngx/pull/13189))
### All App Changes
<details>
<summary>17 changes</summary>
- Chore(deps-dev): Bump postcss from 8.5.6 to 8.5.22 in /src/paperless\_mail/templates in the npm\_and\_yarn group across 1 directory @[dependabot[bot]](https://github.com/apps/dependabot) ([#13236](https://github.com/paperless-ngx/paperless-ngx/pull/13236))
- Fix: selection\_data re-derives the filtered document set 5 times over [@stumpylog](https://github.com/stumpylog) ([#13229](https://github.com/paperless-ngx/paperless-ngx/pull/13229))
- Chore: reword "Settings > Default permissions" description for clarity [@NotaInutilis](https://github.com/NotaInutilis) ([#13232](https://github.com/paperless-ngx/paperless-ngx/pull/13232))
- Chore(deps): Bump pyasn1 from 0.6.3 to 0.6.4 in the uv group across 1 directory @[dependabot[bot]](https://github.com/apps/dependabot) ([#13237](https://github.com/paperless-ngx/paperless-ngx/pull/13237))
- Chore(deps): Bump the uv group across 1 directory with 8 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#13189](https://github.com/paperless-ngx/paperless-ngx/pull/13189))
- Fix: exclude source document from its own RAG similarity results [@stumpylog](https://github.com/stumpylog) ([#13233](https://github.com/paperless-ngx/paperless-ngx/pull/13233))
- Fix: split mailrule maximum\_age clamp into its own migration [@stumpylog](https://github.com/stumpylog) ([#13231](https://github.com/paperless-ngx/paperless-ngx/pull/13231))
- Chore: mark yes in confirm button for translation [@shamoon](https://github.com/shamoon) ([#13225](https://github.com/paperless-ngx/paperless-ngx/pull/13225))
- Fix: correct URL for W001 check [@shamoon](https://github.com/shamoon) ([#13220](https://github.com/paperless-ngx/paperless-ngx/pull/13220))
- Fix: ensure create dialog uses correct attribute type [@shamoon](https://github.com/shamoon) ([#13221](https://github.com/paperless-ngx/paperless-ngx/pull/13221))
- Fix: correct database engine from postgres to postgresql [@OberstVonGatow](https://github.com/OberstVonGatow) ([#13213](https://github.com/paperless-ngx/paperless-ngx/pull/13213))
- Fix: remove unnecessary .distinct() dominating document list queries [@stumpylog](https://github.com/stumpylog) ([#13205](https://github.com/paperless-ngx/paperless-ngx/pull/13205))
- Fix: also wrap non-breaking words in chat messages, handle whitespace [@shamoon](https://github.com/shamoon) ([#13211](https://github.com/paperless-ngx/paperless-ngx/pull/13211))
- Fix: tag/custom-field document\_count scales badly with tag count [@stumpylog](https://github.com/stumpylog) ([#13203](https://github.com/paperless-ngx/paperless-ngx/pull/13203))
- Fix: batch document user\_can\_change checks to avoid per-row N+1 [@stumpylog](https://github.com/stumpylog) ([#13204](https://github.com/paperless-ngx/paperless-ngx/pull/13204))
- Fix: fix app title restoration [@shamoon](https://github.com/shamoon) ([#13208](https://github.com/paperless-ngx/paperless-ngx/pull/13208))
- Fix: handle long wrapping titles in AI chat document list [@shamoon](https://github.com/shamoon) ([#13206](https://github.com/paperless-ngx/paperless-ngx/pull/13206))
</details>
## paperless-ngx 3.0.0
### Breaking Changes
+8 -22
View File
@@ -522,36 +522,22 @@ do CORS calls. Set this to your public domain name.
fail2ban with log entries for failed authorization attempts. Value should be
IP address(es).
By default, this setting also controls allauth's trusted proxy count,
which is set to the number of proxies listed here. Override that default
with [`PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT`](#PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT)
when the list length does not match the number of proxy hops.
This setting also controls allauth's
[`ALLAUTH_TRUSTED_PROXY_COUNT`](https://docs.allauth.org/en/latest/account/configuration.html),
which is set to the number of proxies listed here. Without this,
allauth cannot determine the client IP address for rate limiting when
running behind a reverse proxy, resulting in a `403 Forbidden` on login.
Defaults to empty string.
#### [`PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT=<integer>`](#PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT) {#PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT}
: Sets allauth's
[`ALLAUTH_TRUSTED_PROXY_COUNT`](https://docs.allauth.org/en/latest/common/rate_limits.html#configuration).
This is the number of trusted proxy **hops** represented in each
`X-Forwarded-For` header, not the number of IP addresses through which those
proxies may be reached. For example, a single dual-stack proxy is one hop even
when its IPv4 and IPv6 addresses are both listed in
[`PAPERLESS_TRUSTED_PROXIES`](#PAPERLESS_TRUSTED_PROXIES).
Only trust `X-Forwarded-For` when untrusted clients cannot connect directly
to Paperless-ngx.
Defaults to the number of entries in `PAPERLESS_TRUSTED_PROXIES`.
#### [`PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER=<header-name>`](#PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER) {#PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER}
: Sets allauth's
[`ALLAUTH_TRUSTED_CLIENT_IP_HEADER`](https://docs.allauth.org/en/latest/common/rate_limits.html#configuration).
[`ALLAUTH_TRUSTED_CLIENT_IP_HEADER`](https://docs.allauth.org/en/latest/account/configuration.html).
Use this when your reverse proxy sets a dedicated header for the real
client IP instead of `X-Forwarded-For`, for example `X-Real-IP` (nginx)
or `CF-Connecting-IP` (Cloudflare). When set, this takes precedence over
`PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT`.
[`PAPERLESS_TRUSTED_PROXIES`](#PAPERLESS_TRUSTED_PROXIES).
Defaults to none.
@@ -2135,7 +2121,7 @@ used with the OpenAI-compatible backend to target a custom provider or local gat
Defaults to None.
#### [`PAPERLESS_AI_LLM_OUTPUT_LANGUAGE=<str>`](#PAPERLESS_AI_LLM_OUTPUT_LANGUAGE) {#PAPERLESS_AI_LLM_OUTPUT_LANGUAGE}
### [`PAPERLESS_AI_LLM_OUTPUT_LANGUAGE=<str>`](#PAPERLESS_AI_LLM_OUTPUT_LANGUAGE) {#PAPERLESS_AI_LLM_OUTPUT_LANGUAGE}
: The language to use for AI suggestions (results may vary by LLM model). If not supplied, defaults to the user's UI language setting or None.
+1 -4
View File
@@ -324,11 +324,8 @@ option since v1.8.0.
Allauth changed how it determines the client IP address for login rate limiting. Users running
behind a reverse proxy may need to set
[`PAPERLESS_TRUSTED_PROXIES`](configuration.md#PAPERLESS_TRUSTED_PROXIES),
[`PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT`](configuration.md#PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT),
[`PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER`](configuration.md#PAPERLESS_ALLAUTH_TRUSTED_CLIENT_IP_HEADER),
or a combination of these settings to avoid `403 Forbidden` errors on login.
The proxy count is the number of proxy hops in `X-Forwarded-For`, which may
differ from the number of configured proxy IP addresses.
or both, to avoid `403 Forbidden` errors on login.
## Minimum CPU Requirements (NumPy Baseline)
-8
View File
@@ -262,14 +262,6 @@ to a positive number to enable polling and disable native filesystem notificatio
and copy the contents to the home directory of the user you created
earlier (`/opt/paperless`).
!!! note
If you are updating an existing bare-metal installation rather than
installing for the first time, see the
[bare metal update instructions](administration.md#bare-metal-updating)
instead -- extracting a new release on top of an old one can leave
behind stale files from the previous version.
Optional: If you cloned the Git repository, you will need to
compile the frontend yourself. See [here](development.md#front-end-development)
and use the `build` step, not `serve`.
View File
-123
View File
@@ -1,123 +0,0 @@
# profiling/harness.py
from __future__ import annotations
import json
import subprocess
import time
from dataclasses import dataclass
from datetime import UTC
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import Generic
from typing import TypeVar
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
if TYPE_CHECKING:
from collections.abc import Callable
from django.db.models import QuerySet
from profiling.seed import ScaleProfile
T = TypeVar("T")
def require_postgres() -> None:
if connection.vendor != "postgresql":
pytest.skip(
"Profiling requires PostgreSQL to reflect production query "
"planning; SQLite does not reproduce the varchar-cast planner "
"pathology this work fixes.",
)
@dataclass(frozen=True, slots=True)
class ProfileResult(Generic[T]):
best_seconds: float
all_seconds: tuple[float, ...]
query_count: int
result: T
def run_profile(fn: Callable[[], T], *, repeat: int = 5) -> ProfileResult[T]:
require_postgres()
all_seconds: list[float] = []
result: T | None = None
query_count = 0
for i in range(repeat):
with CaptureQueriesContext(connection) as ctx:
start = time.perf_counter()
result = fn()
all_seconds.append(time.perf_counter() - start)
if i == repeat - 1:
query_count = len(ctx.captured_queries)
assert result is not None # repeat >= 1 guarantees at least one assignment
return ProfileResult(
best_seconds=min(all_seconds),
all_seconds=tuple(all_seconds),
query_count=query_count,
result=result,
)
def capture_explain_analyze(queryset: QuerySet[Any]) -> str:
if connection.vendor != "postgresql":
raise RuntimeError("EXPLAIN ANALYZE capture is Postgres-only")
sql, params = queryset.query.sql_with_params()
with connection.cursor() as cur:
cur.execute(f"EXPLAIN ANALYZE {sql}", params)
return "\n".join(row[0] for row in cur.fetchall())
def _current_git_ref() -> str:
result = subprocess.run(
["git", "rev-parse", "--short", "HEAD"],
capture_output=True,
text=True,
check=False,
)
return result.stdout.strip() or "unknown"
def append_profiling_history(
*,
stage: str,
scenario: str,
best_seconds: float,
query_count: int,
scale: ScaleProfile,
code_ref: str | None = None,
) -> None:
"""
Append one line to profiling/results/history.jsonl -- an append-only,
cross-session record of every profiling run. Unlike the per-stage
before/after snapshot JSON files (which the next run of the same stage
overwrites), this never gets clobbered, so a profiling effort picked
back up days later has a full timeline to resume from instead of only
the single most recent comparison.
``code_ref`` should identify the *production* code under test (a short
SHA on the feature branch, not this `profiling` branch) -- pass it
explicitly when known. Left as ``None``, it falls back to this repo's
current ``git rev-parse --short HEAD``, which is only meaningful if
you're actually running from a checkout where that reflects the code
being measured.
"""
results_dir = Path(__file__).parent / "results"
results_dir.mkdir(exist_ok=True)
entry = {
"timestamp": datetime.now(UTC).isoformat(),
"code_ref": code_ref or _current_git_ref(),
"stage": stage,
"scenario": scenario,
"scale": scale,
"best_seconds": best_seconds,
"query_count": query_count,
}
with (results_dir / "history.jsonl").open("a") as f:
f.write(json.dumps(entry) + "\n")
-18
View File
@@ -1,18 +0,0 @@
[tool.pytest]
ini_options.pythonpath = [ "../src", "..", "." ]
ini_options.markers = [
"""\
profiling: Profiling scripts comparing old vs new query patterns; require a throwaway PostgreSQL container, not run \
as part of the normal test suite\
""",
]
ini_options.DJANGO_SETTINGS_MODULE = "paperless.settings"
[tool.pytest_env]
# Standalone config: this directory must be independently runnable without
# the rest of the dev stack (Redis, etc) present -- only Postgres is required,
# via run_with_postgres.sh.
PAPERLESS_SECRET_KEY = "profiling-only-insecure-key"
PAPERLESS_DISABLE_DBHANDLER = "true"
PAPERLESS_CACHE_BACKEND = "django.core.cache.backends.locmem.LocMemCache"
PAPERLESS_CHANNELS_BACKEND = "channels.layers.InMemoryChannelLayer"
-8
View File
@@ -1,8 +0,0 @@
{"timestamp": "2026-07-27T22:47:46.677504+00:00", "code_ref": "test-run", "stage": "self-check", "scenario": "test_append_profiling_history_writes_a_valid_json_line", "scale": "medium", "best_seconds": 0.001, "query_count": 0}
{"timestamp": "2026-07-27T22:49:03.762638+00:00", "code_ref": "test-run", "stage": "self-check", "scenario": "test_append_profiling_history_writes_a_valid_json_line", "scale": "medium", "best_seconds": 0.001, "query_count": 0}
{"timestamp": "2026-07-27T23:01:49.777269+00:00", "code_ref": "0506dcad0", "stage": "stage1", "scenario": "get_objects_for_user_owner_aware_document_baseline", "scale": "medium", "best_seconds": 0.025123266968876123, "query_count": 1}
{"timestamp": "2026-07-28T01:38:32.706519+00:00", "code_ref": "e80ede4cb", "stage": "stage1", "scenario": "get_objects_for_user_owner_aware_document_baseline", "scale": "medium", "best_seconds": 31.46481539506931, "query_count": 1}
{"timestamp": "2026-07-28T03:53:46.770669+00:00", "code_ref": "d4fa85237", "stage": "stage1", "scenario": "get_objects_for_user_owner_aware_document_baseline", "scale": "medium", "best_seconds": 31.671881378046237, "query_count": 1}
{"timestamp": "2026-07-28T04:02:41.942168+00:00", "code_ref": "d4fa85237", "stage": "stage1", "scenario": "permitted_document_ids_after_stage1", "scale": "medium", "best_seconds": 0.07956207206007093, "query_count": 1}
{"timestamp": "2026-07-28T23:30:28.602501+00:00", "code_ref": "86846255f", "stage": "stage2", "scenario": "per_row_checker_vs_resolved_set_old", "scale": "medium", "best_seconds": 3.4628250940004364, "query_count": 692}
{"timestamp": "2026-07-28T23:30:28.606987+00:00", "code_ref": "86846255f", "stage": "stage2", "scenario": "per_row_checker_vs_resolved_set_new", "scale": "medium", "best_seconds": 0.10436739504802972, "query_count": 1}
-9
View File
@@ -1,9 +0,0 @@
{
"permitted_document_ids": {
"best_seconds": 0.07956207206007093,
"query_count": 1
},
"baseline_best_seconds": 31.671881378046237,
"baseline_query_count": 1,
"speedup_x": 398.07763370131113
}
@@ -1,58 +0,0 @@
Index Scan Backward using documents_document_created_bedd0818 on documents_document v0 (cost=91.31..99.33 rows=1 width=2900) (actual time=68.340..77.517 rows=12024.00 loops=1)
Filter: ((deleted_at IS NULL) AND ((owner_id = 12) OR (owner_id IS NULL) OR (ANY (id = (hashed SubPlan 1).col1))))
Rows Removed by Filter: 7976
Index Searches: 1
Buffers: shared hit=74978
SubPlan 1
-> Unique (cost=91.04..91.05 rows=2 width=4) (actual time=64.698..66.622 rows=9136.00 loops=1)
Buffers: shared hit=73487
-> Sort (cost=91.04..91.05 rows=2 width=4) (actual time=64.697..65.202 rows=9262.00 loops=1)
Sort Key: ((u0.object_pk)::integer)
Sort Method: quicksort Memory: 385kB
Buffers: shared hit=73487
-> Append (cost=0.27..91.03 rows=2 width=4) (actual time=0.026..63.201 rows=9262.00 loops=1)
Buffers: shared hit=73487
-> Nested Loop (cost=0.27..12.79 rows=1 width=4) (actual time=0.026..1.834 rows=237.00 loops=1)
Join Filter: (u0.permission_id = u1.id)
Buffers: shared hit=696
-> Index Only Scan using guardian_userobjectpermi_user_id_permission_id_ob_b0b3d2fc_uniq on guardian_userobjectpermission u0 (cost=0.27..8.29 rows=1 width=520) (actual time=0.012..0.131 rows=237.00 loops=1)
Index Cond: (user_id = 12)
Heap Fetches: 237
Index Searches: 1
Buffers: shared hit=222
-> Seq Scan on auth_permission u1 (cost=0.00..4.49 rows=1 width=4) (actual time=0.006..0.006 rows=1.00 loops=237)
Filter: (((codename)::text = 'view_document'::text) AND (content_type_id = 17))
Rows Removed by Filter: 67
Buffers: shared hit=474
-> Nested Loop (cost=4.73..78.23 rows=1 width=4) (actual time=1.119..60.528 rows=9025.00 loops=1)
Buffers: shared hit=72791
-> Nested Loop (cost=4.59..78.05 rows=1 width=524) (actual time=1.115..50.521 rows=9025.00 loops=1)
Buffers: shared hit=54741
-> Nested Loop (cost=4.44..73.62 rows=24 width=520) (actual time=1.109..13.226 rows=45387.00 loops=1)
Buffers: shared hit=329
-> Seq Scan on auth_permission u4 (cost=0.00..4.49 rows=1 width=4) (actual time=0.008..0.017 rows=1.00 loops=1)
Filter: (((codename)::text = 'view_document'::text) AND (content_type_id = 17))
Rows Removed by Filter: 165
Buffers: shared hit=2
-> Bitmap Heap Scan on guardian_groupobjectpermission u0_1 (cost=4.44..68.93 rows=20 width=524) (actual time=1.100..8.095 rows=45387.00 loops=1)
Recheck Cond: (permission_id = u4.id)
Heap Blocks: exact=290
Buffers: shared hit=327
-> Bitmap Index Scan on guardian_groupobjectpermission_permission_id_36572738 (cost=0.00..4.43 rows=20 width=0) (actual time=1.052..1.053 rows=45387.00 loops=1)
Index Cond: (permission_id = u4.id)
Index Searches: 1
Buffers: shared hit=37
-> Index Only Scan using auth_user_groups_user_id_group_id_94350c0c_uniq on auth_user_groups u2 (cost=0.15..0.18 rows=1 width=4) (actual time=0.001..0.001 rows=0.20 loops=45387)
Index Cond: ((user_id = 12) AND (group_id = u0_1.group_id))
Heap Fetches: 9025
Index Searches: 45387
Buffers: shared hit=54412
-> Index Only Scan using auth_group_pkey on auth_group u1_1 (cost=0.14..0.17 rows=1 width=4) (actual time=0.001..0.001 rows=1.00 loops=9025)
Index Cond: (id = u0_1.group_id)
Heap Fetches: 9025
Index Searches: 9025
Buffers: shared hit=18050
Planning:
Buffers: shared hit=2
Planning Time: 1.089 ms
Execution Time: 78.020 ms
-6
View File
@@ -1,6 +0,0 @@
{
"get_objects_for_user_owner_aware_document": {
"best_seconds": 31.671881378046237,
"query_count": 1
}
}
@@ -1,68 +0,0 @@
Sort (cost=3790.80..3790.82 rows=9 width=2900) (actual time=39120.166..39120.831 rows=12024.00 loops=1)
Sort Key: documents_document.created DESC
Sort Method: quicksort Memory: 3230kB
Buffers: shared hit=8598270
-> Seq Scan on documents_document (cost=2550.72..3790.65 rows=9 width=2900) (actual time=0.009..39114.720 rows=12024.00 loops=1)
Filter: ((deleted_at IS NULL) AND ((owner_id = 2) OR (owner_id IS NULL) OR (ANY (id = (hashed SubPlan 1).col1)) OR (ANY (id = (hashed SubPlan 2).col1))))
Rows Removed by Filter: 7976
Buffers: shared hit=8598270
SubPlan 1
-> Nested Loop Semi Join (cost=0.00..1245.63 rows=1 width=8) (actual time=4.254..969.033 rows=237.00 loops=1)
Join Filter: ((((v0.object_pk)::bigint)::character varying)::text = (((u0.id)::bigint)::character varying)::text)
Rows Removed by Join Filter: 2539276
Buffers: shared hit=214835
-> Nested Loop (cost=0.00..23.30 rows=1 width=516) (actual time=0.031..2.425 rows=237.00 loops=1)
Join Filter: (v0.permission_id = v2.id)
Buffers: shared hit=490
-> Seq Scan on guardian_userobjectpermission v0 (cost=0.00..18.80 rows=1 width=520) (actual time=0.021..0.424 rows=237.00 loops=1)
Filter: (user_id = 2)
Rows Removed by Filter: 2120
Buffers: shared hit=16
-> Seq Scan on auth_permission v2 (cost=0.00..4.49 rows=1 width=4) (actual time=0.006..0.006 rows=1.00 loops=237)
Filter: ((content_type_id = 17) AND ((codename)::text = 'view_document'::text))
Rows Removed by Filter: 67
Buffers: shared hit=474
-> Seq Scan on documents_document u0 (cost=0.00..1221.96 rows=12 width=4) (actual time=0.002..1.917 rows=10715.24 loops=237)
Filter: (deleted_at IS NULL)
Buffers: shared hit=214345
SubPlan 2
-> Nested Loop Semi Join (cost=4.73..1305.09 rows=1 width=8) (actual time=6.729..38127.912 rows=9025.00 loops=1)
Join Filter: ((((v0_1.object_pk)::bigint)::character varying)::text = (((u0_2.id)::bigint)::character varying)::text)
Rows Removed by Join Filter: 98955406
Buffers: shared hit=8382237
-> Nested Loop Semi Join (cost=4.73..82.77 rows=1 width=516) (actual time=1.109..137.661 rows=9025.00 loops=1)
Buffers: shared hit=145515
-> Nested Loop (cost=4.44..73.62 rows=24 width=520) (actual time=1.090..18.002 rows=45387.00 loops=1)
Buffers: shared hit=329
-> Seq Scan on auth_permission v2_1 (cost=0.00..4.49 rows=1 width=4) (actual time=0.011..0.023 rows=1.00 loops=1)
Filter: (((codename)::text = 'view_document'::text) AND (content_type_id = 17))
Rows Removed by Filter: 165
Buffers: shared hit=2
-> Bitmap Heap Scan on guardian_groupobjectpermission v0_1 (cost=4.44..68.93 rows=20 width=524) (actual time=1.077..10.777 rows=45387.00 loops=1)
Recheck Cond: (permission_id = v2_1.id)
Heap Blocks: exact=290
Buffers: shared hit=327
-> Bitmap Index Scan on guardian_groupobjectpermission_permission_id_36572738 (cost=0.00..4.43 rows=20 width=0) (actual time=1.026..1.026 rows=45387.00 loops=1)
Index Cond: (permission_id = v2_1.id)
Index Searches: 1
Buffers: shared hit=37
-> Nested Loop (cost=0.30..0.37 rows=1 width=8) (actual time=0.002..0.002 rows=0.20 loops=45387)
Join Filter: (u0_1.id = u1.group_id)
Buffers: shared hit=145186
-> Index Only Scan using auth_group_pkey on auth_group u0_1 (cost=0.14..0.17 rows=1 width=4) (actual time=0.001..0.001 rows=1.00 loops=45387)
Index Cond: (id = v0_1.group_id)
Heap Fetches: 45387
Index Searches: 45387
Buffers: shared hit=90774
-> Index Only Scan using auth_user_groups_user_id_group_id_94350c0c_uniq on auth_user_groups u1 (cost=0.15..0.18 rows=1 width=4) (actual time=0.001..0.001 rows=0.20 loops=45387)
Index Cond: ((user_id = 2) AND (group_id = v0_1.group_id))
Heap Fetches: 9025
Index Searches: 45387
Buffers: shared hit=54412
-> Seq Scan on documents_document u0_2 (cost=0.00..1221.96 rows=12 width=4) (actual time=0.002..1.956 rows=10965.59 loops=9025)
Filter: (deleted_at IS NULL)
Buffers: shared hit=8236722
Planning:
Buffers: shared hit=3
Planning Time: 1.119 ms
Execution Time: 39121.312 ms
-48
View File
@@ -1,48 +0,0 @@
#!/usr/bin/env bash
# profiling/run_with_postgres.sh
#
# Starts a PostgreSQL 18 container (matching
# docker/compose/docker-compose.postgres.yml's image) if one isn't already
# running, waits for it to accept connections, force-terminates any
# leftover connections from a previous run that crashed or was killed, then
# runs the given command against it with the right PAPERLESS_DB* env vars
# set. The container is intentionally left running afterward -- see
# stop_postgres.sh to tear it down explicitly once you're done profiling
# for the session.
set -euo pipefail
CONTAINER_NAME="pngx-profiling-pg"
HOST_PORT="55432"
if ! docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
docker run --rm -d \
--name "$CONTAINER_NAME" \
-e POSTGRES_DB=paperless \
-e POSTGRES_USER=paperless \
-e POSTGRES_PASSWORD=paperless \
-p "${HOST_PORT}:5432" \
docker.io/library/postgres:18 >/dev/null
echo "Started profiling Postgres container '$CONTAINER_NAME' on port $HOST_PORT (left running -- see profiling/stop_postgres.sh)." >&2
fi
until docker exec "$CONTAINER_NAME" pg_isready -U paperless >/dev/null 2>&1; do
sleep 1
done
# Reap any connections left over from a previous run that crashed, was
# killed, or timed out mid-query -- without this, a persistent container
# can accumulate stuck backends that block later test-database
# create/drop operations indefinitely.
docker exec "$CONTAINER_NAME" psql -U paperless -d paperless -v ON_ERROR_STOP=0 -c "
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname IN ('paperless', 'test_paperless')
AND pid <> pg_backend_pid();
" >/dev/null 2>&1 || true
PAPERLESS_DBHOST=localhost \
PAPERLESS_DBNAME=paperless \
PAPERLESS_DBUSER=paperless \
PAPERLESS_DBPASS=paperless \
PAPERLESS_DBPORT="${HOST_PORT}" \
"$@"
-199
View File
@@ -1,199 +0,0 @@
# profiling/seed.py
from __future__ import annotations
import random
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import Literal
from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from guardian.shortcuts import assign_perm
from documents.models import Document
from documents.tests.factories import CorrespondentFactory
from documents.tests.factories import DocumentFactory
from documents.tests.factories import DocumentTypeFactory
from documents.tests.factories import StoragePathFactory
from documents.tests.factories import TagFactory
if TYPE_CHECKING:
from documents.models import Correspondent
from documents.models import DocumentType
from documents.models import StoragePath
from documents.models import Tag
ScaleProfile = Literal["medium", "large"]
# Fraction of seeded documents given a real owner; the remainder stays
# owner=None, mirroring a real install's mix of owned and legacy/imported
# unowned documents.
_OWNED_FRACTION = 0.9
class _ScaleCounts:
__slots__ = (
"correspondents",
"document_types",
"documents",
"groups",
"storage_paths",
"tags",
"users",
)
def __init__(
self,
*,
documents: int,
tags: int,
correspondents: int,
document_types: int,
storage_paths: int,
users: int,
groups: int,
) -> None:
self.documents = documents
self.tags = tags
self.correspondents = correspondents
self.document_types = document_types
self.storage_paths = storage_paths
self.users = users
self.groups = groups
_SCALE_PROFILES: dict[ScaleProfile, _ScaleCounts] = {
"medium": _ScaleCounts(
documents=20_000,
tags=100,
correspondents=300,
document_types=50,
storage_paths=20,
users=10,
groups=5,
),
"large": _ScaleCounts(
documents=360_000,
tags=1_000,
correspondents=5_000,
document_types=300,
storage_paths=50,
users=25,
groups=10,
),
}
# Ratios measured from a real install (discussion #13276): 1,414 user-perm
# rows / 27,232 group-perm rows over 12,000 documents.
_USER_PERM_ROWS_PER_DOC = 1_414 / 12_000
_GROUP_PERM_ROWS_PER_DOC = 27_232 / 12_000
def _sample_distinct_pairs(
rng: random.Random,
n_subjects: int,
n_objects: int,
count: int,
) -> set[tuple[int, int]]:
"""
Rejection-sample `count` distinct (subject_index, object_index) pairs.
`count` is always well under `n_subjects * n_objects` for every scale
profile, so this terminates quickly.
"""
if count > n_subjects * n_objects:
msg = (
f"cannot sample {count} distinct pairs from only "
f"{n_subjects * n_objects} possible (subject, object) slots"
)
raise ValueError(msg)
pairs: set[tuple[int, int]] = set()
while len(pairs) < count:
pairs.add((rng.randrange(n_subjects), rng.randrange(n_objects)))
return pairs
@dataclass(frozen=True, slots=True)
class SeededData:
users: tuple[User, ...]
groups: tuple[Group, ...]
documents: tuple[Document, ...]
tags: tuple[Tag, ...]
correspondents: tuple[Correspondent, ...]
document_types: tuple[DocumentType, ...]
storage_paths: tuple[StoragePath, ...]
def seed_permission_dataset(
scale: ScaleProfile = "medium",
*,
seed: int = 1337,
) -> SeededData:
"""
Build a dataset whose document count and guardian-permission-row ratios
mirror real bug reports, so profiling results are representative of
actual large installs rather than an arbitrary small fixture.
"""
counts = _SCALE_PROFILES[scale]
rng = random.Random(seed)
users = tuple(
User.objects.create_user(username=f"profile_user_{i}")
for i in range(counts.users)
)
groups = tuple(
Group.objects.create(name=f"profile_group_{i}") for i in range(counts.groups)
)
for user in users:
user.groups.add(rng.choice(groups))
documents = list(DocumentFactory.create_batch(counts.documents))
# Assign realistic ownership -- without this, every document stays
# owner=None and the "owned or unowned" visibility clause is trivially
# true for every row, so no guardian permission check ever needs to run
# (see the amendment note above this code block).
owned_documents = []
for document in documents:
if rng.random() < _OWNED_FRACTION:
document.owner = rng.choice(users)
owned_documents.append(document)
Document.objects.bulk_update(owned_documents, ["owner"], batch_size=2_000)
documents = tuple(documents)
tags = tuple(TagFactory.create_batch(counts.tags))
correspondents = tuple(CorrespondentFactory.create_batch(counts.correspondents))
document_types = tuple(DocumentTypeFactory.create_batch(counts.document_types))
storage_paths = tuple(StoragePathFactory.create_batch(counts.storage_paths))
n_user_perms = round(counts.documents * _USER_PERM_ROWS_PER_DOC)
n_group_perms = round(counts.documents * _GROUP_PERM_ROWS_PER_DOC)
# Grant permissions only on OWNED documents -- a grant on an already-
# unowned document is a no-op for visibility (unowned documents are
# visible to everyone regardless), so restricting the pool here is what
# makes each assign_perm() call actually matter for someone, and is
# what exercises the guardian-permission-join code path this profiling
# effort exists to measure.
for user_idx, owned_idx in _sample_distinct_pairs(
rng,
len(users),
len(owned_documents),
n_user_perms,
):
assign_perm("view_document", users[user_idx], owned_documents[owned_idx])
for group_idx, owned_idx in _sample_distinct_pairs(
rng,
len(groups),
len(owned_documents),
n_group_perms,
):
assign_perm("view_document", groups[group_idx], owned_documents[owned_idx])
return SeededData(
users=users,
groups=groups,
documents=documents,
tags=tags,
correspondents=correspondents,
document_types=document_types,
storage_paths=storage_paths,
)
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/env bash
# profiling/stop_postgres.sh
#
# Explicit, manual teardown of the profiling Postgres container. Not called
# automatically by run_with_postgres.sh -- run this yourself once you're
# done with a profiling session, so a seeded `large`-scale dataset can be
# reused across several script runs first. Terminates any active backends
# before stopping, so a stuck query can't stall `docker stop` waiting for
# a graceful Postgres shutdown that never comes.
set -euo pipefail
CONTAINER_NAME="pngx-profiling-pg"
if docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
docker exec "$CONTAINER_NAME" psql -U paperless -d paperless -v ON_ERROR_STOP=0 -c "
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid <> pg_backend_pid();
" >/dev/null 2>&1 || true
docker stop "$CONTAINER_NAME" >/dev/null
echo "Stopped and removed profiling Postgres container."
else
echo "No profiling Postgres container was running."
fi
-74
View File
@@ -1,74 +0,0 @@
# profiling/test_harness_self_check.py
from __future__ import annotations
import json
from pathlib import Path
import pytest
from profiling.harness import append_profiling_history
from profiling.harness import require_postgres
from profiling.harness import run_profile
from profiling.seed import seed_permission_dataset
@pytest.mark.profiling
@pytest.mark.django_db
def test_seed_medium_scale_produces_expected_counts() -> None:
require_postgres()
data = seed_permission_dataset(scale="medium")
assert len(data.documents) == 20_000
assert len(data.tags) == 100
assert len(data.correspondents) == 300
assert len(data.document_types) == 50
assert len(data.storage_paths) == 20
from guardian.models import GroupObjectPermission
from guardian.models import UserObjectPermission
user_perm_count = UserObjectPermission.objects.count()
group_perm_count = GroupObjectPermission.objects.count()
# within 5% of the ratio-derived target -- exact counts depend on random
# sampling without replacement, see seed.py
assert 2_242 <= user_perm_count <= 2_478
assert 43_111 <= group_perm_count <= 47_649
@pytest.mark.profiling
@pytest.mark.django_db
def test_run_profile_reports_query_count_and_timing() -> None:
require_postgres()
def trivial() -> list[int]:
from documents.models import Document
return list(Document.objects.values_list("id", flat=True)[:1])
result = run_profile(trivial, repeat=3)
assert result.best_seconds >= 0
assert len(result.all_seconds) == 3
assert result.query_count >= 1
def test_append_profiling_history_writes_a_valid_json_line() -> None:
history_path = Path(__file__).parent / "results" / "history.jsonl"
before_lines = (
history_path.read_text().splitlines() if history_path.exists() else []
)
append_profiling_history(
stage="self-check",
scenario="test_append_profiling_history_writes_a_valid_json_line",
best_seconds=0.001,
query_count=0,
scale="medium",
code_ref="test-run",
)
after_lines = history_path.read_text().splitlines()
assert len(after_lines) == len(before_lines) + 1
entry = json.loads(after_lines[-1])
assert entry["stage"] == "self-check"
assert entry["code_ref"] == "test-run"
assert entry["scale"] == "medium"
assert "timestamp" in entry
-125
View File
@@ -1,125 +0,0 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
from documents.models import Document
from documents.permissions import get_objects_for_user_owner_aware
from documents.permissions import permitted_document_ids
from profiling.harness import append_profiling_history
from profiling.harness import capture_explain_analyze
from profiling.harness import require_postgres
from profiling.harness import run_profile
from profiling.seed import seed_permission_dataset
RESULTS_DIR = Path(__file__).parent / "results"
@pytest.mark.profiling
@pytest.mark.django_db
def test_profile_get_objects_for_user_owner_aware_document_baseline() -> None:
require_postgres()
data = seed_permission_dataset(scale="medium")
user = data.users[0]
def call() -> list[int]:
return list(
get_objects_for_user_owner_aware(
user,
"documents.view_document",
Document,
).values_list("id", flat=True),
)
profile = run_profile(call, repeat=3)
plan = capture_explain_analyze(
get_objects_for_user_owner_aware(user, "documents.view_document", Document),
)
RESULTS_DIR.mkdir(exist_ok=True)
(RESULTS_DIR / "stage1_baseline.json").write_text(
json.dumps(
{
"get_objects_for_user_owner_aware_document": {
"best_seconds": profile.best_seconds,
"query_count": profile.query_count,
},
},
indent=2,
),
)
(RESULTS_DIR / "stage1_baseline_explain.txt").write_text(plan)
append_profiling_history(
stage="stage1",
scenario="get_objects_for_user_owner_aware_document_baseline",
best_seconds=profile.best_seconds,
query_count=profile.query_count,
scale="medium",
)
print(f"\nBASELINE best={profile.best_seconds:.4f}s queries={profile.query_count}") # noqa: T201
print(plan) # noqa: T201
@pytest.mark.profiling
@pytest.mark.django_db
def test_profile_permitted_document_ids_after_stage1() -> None:
require_postgres()
data = seed_permission_dataset(scale="medium")
user = data.users[0]
def call():
return list(
Document.objects.filter(id__in=permitted_document_ids(user)).values_list(
"id",
flat=True,
),
)
profile = run_profile(call, repeat=3)
plan = capture_explain_analyze(
Document.objects.filter(id__in=permitted_document_ids(user)),
)
baseline = json.loads((RESULTS_DIR / "stage1_baseline.json").read_text())
baseline_seconds = baseline["get_objects_for_user_owner_aware_document"][
"best_seconds"
]
baseline_queries = baseline["get_objects_for_user_owner_aware_document"][
"query_count"
]
(RESULTS_DIR / "stage1_after.json").write_text(
json.dumps(
{
"permitted_document_ids": {
"best_seconds": profile.best_seconds,
"query_count": profile.query_count,
},
"baseline_best_seconds": baseline_seconds,
"baseline_query_count": baseline_queries,
"speedup_x": baseline_seconds / profile.best_seconds
if profile.best_seconds
else None,
},
indent=2,
),
)
(RESULTS_DIR / "stage1_after_explain.txt").write_text(plan)
append_profiling_history(
stage="stage1",
scenario="permitted_document_ids_after_stage1",
best_seconds=profile.best_seconds,
query_count=profile.query_count,
scale="medium",
)
print(f"\nBEFORE best={baseline_seconds:.4f}s queries={baseline_queries}") # noqa: T201
print(f"AFTER best={profile.best_seconds:.4f}s queries={profile.query_count}") # noqa: T201
# Hard gate: must not be a regression, and should be meaningfully faster
# at this scale (the whole point of this stage).
assert profile.best_seconds < baseline_seconds
assert profile.query_count <= baseline_queries
-64
View File
@@ -1,64 +0,0 @@
# profiling/test_stage2_document_loops.py
from __future__ import annotations
import pytest
from documents.permissions import has_perms_owner_aware
from documents.permissions import permitted_document_ids
from profiling.harness import append_profiling_history
from profiling.harness import require_postgres
from profiling.harness import run_profile
from profiling.seed import seed_permission_dataset
@pytest.mark.profiling
@pytest.mark.django_db
def test_profile_per_row_checker_vs_resolved_set_for_bulk_operation() -> None:
require_postgres()
data = seed_permission_dataset(scale="medium")
user = data.users[0]
# simulate a bulk operation over 500 documents the user can actually see
# (bulk-edit/share/trash all operate on a user's own selection) -- built
# from confirmed-visible documents so all() can't short-circuit unfairly
# in either old_style or new_style, see amendment note above
permitted_ids = set(permitted_document_ids(user))
batch = [doc for doc in data.documents if doc.pk in permitted_ids][:500]
assert len(batch) == 500, (
f"expected at least 500 visible documents to build a fair batch, "
f"got {len(batch)} -- scale profile or seed ratios may need adjusting"
)
def old_style():
return all(has_perms_owner_aware(user, "view_document", doc) for doc in batch)
def new_style():
permitted = set(permitted_document_ids(user))
return all(doc.pk in permitted for doc in batch)
old_profile = run_profile(old_style, repeat=3)
new_profile = run_profile(new_style, repeat=3)
append_profiling_history(
stage="stage2",
scenario="per_row_checker_vs_resolved_set_old",
best_seconds=old_profile.best_seconds,
query_count=old_profile.query_count,
scale="medium",
)
append_profiling_history(
stage="stage2",
scenario="per_row_checker_vs_resolved_set_new",
best_seconds=new_profile.best_seconds,
query_count=new_profile.query_count,
scale="medium",
)
print( # noqa: T201
f"\nOLD (per-row checker): best={old_profile.best_seconds:.4f}s queries={old_profile.query_count}",
)
print( # noqa: T201
f"NEW (resolved set): best={new_profile.best_seconds:.4f}s queries={new_profile.query_count}",
)
assert new_profile.query_count < old_profile.query_count
assert new_profile.best_seconds < old_profile.best_seconds
+11 -2
View File
@@ -105,7 +105,7 @@ docs = [
]
lint = [
"prek~=0.3.10",
"ruff~=0.15.20",
"ruff~=0.16.0",
]
testing = [
"daphne",
@@ -184,12 +184,16 @@ line-ending = "lf"
[tool.ruff.lint]
# https://docs.astral.sh/ruff/rules/
extend-select = [
"B", # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b
"COM", # https://docs.astral.sh/ruff/rules/#flake8-commas-com
"DTZ", # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz
"PERF", # https://docs.astral.sh/ruff/rules/#perflint-perf
"S324", # https://docs.astral.sh/ruff/rules/hashlib-insecure-hash-functions/
"DJ", # https://docs.astral.sh/ruff/rules/#flake8-django-dj
"EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe
"FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt
"FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly
"G201", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
"G", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
"I", # https://docs.astral.sh/ruff/rules/#isort-i
"ICN", # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn
"INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp
@@ -209,10 +213,15 @@ extend-select = [
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
]
ignore = [
"BLE001", # blind except: too invasive to fix wholesale on ruff 0.16's default bump; revisit incrementally
"DJ001",
"G004", # f-strings in logging: accepted style in this codebase
"PLC0415",
"RUF012",
"SIM105",
"TRY002", # create-your-own-exception: too invasive to fix wholesale on ruff 0.16's default bump; revisit incrementally
"TRY201", # verbose-raise: too invasive to fix wholesale on ruff 0.16's default bump; revisit incrementally
"TRY401", # redundant exception in logging.exception: too invasive to fix wholesale on ruff 0.16's default bump; revisit incrementally
]
# Migrations
per-file-ignores."*/migrations/*.py" = [
@@ -1,4 +1,4 @@
import { expect, test, type WebSocketRoute } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'node:path'
const REQUESTS_HAR = path.join(__dirname, 'requests/api-document-detail.har')
@@ -95,60 +95,3 @@ test('should support quick filters', async ({ page }) => {
.click()
await expect(page).toHaveURL(/tags__id__all=4&sort=created&reverse=1&page=1/)
})
test('should finish reloading the preview after a remote document update', async ({
page,
}) => {
let resolveStatusSocket: (socket: WebSocketRoute) => void
const statusSocketReady = new Promise<WebSocketRoute>((resolve) => {
resolveStatusSocket = resolve
})
await page.routeWebSocket(/\/ws\/status\/$/, (socket) => {
resolveStatusSocket(socket)
})
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
let previewRequestCount = 0
page.on('request', (request) => {
if (request.url().includes('/api/documents/175/preview/')) {
previewRequestCount++
}
})
await page.goto('/documents/175/details')
await page.locator('pngx-document-detail').waitFor()
await expect(page.getByTitle('Storage path', { exact: true })).toHaveText(
/\w+/
)
const previewWasLoaded = await page.evaluate(() => {
const detail = document.querySelector('pngx-document-detail')
const component = (window as any).ng.getComponent(detail)
component.pdfPreviewLoaded({ numPages: 1 })
return component.previewLoaded()
})
expect(previewWasLoaded).toBe(true)
const previewRequestsBeforeReload = previewRequestCount
const statusSocket = await statusSocketReady
const documentReloaded = page.waitForResponse(
(response) =>
response.url().includes('/api/documents/175/?full_perms=true') &&
response.request().method() === 'GET'
)
statusSocket.send(
JSON.stringify({
type: 'document_updated',
data: {
document_id: 175,
modified: '2026-07-26T20:00:00Z',
},
})
)
await documentReloaded
await expect(
page.getByText('Document reloaded with latest changes.').first()
).toBeVisible()
await expect
.poll(() => previewRequestCount)
.toBeGreaterThan(previewRequestsBeforeReload + 1)
})
+49 -49
View File
@@ -1277,7 +1277,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1791</context>
<context context-type="linenumber">1787</context>
</context-group>
</trans-unit>
<trans-unit id="1577733187050997705" datatype="html">
@@ -2184,7 +2184,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">657</context>
<context context-type="linenumber">653</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
@@ -3087,11 +3087,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1409</context>
<context context-type="linenumber">1405</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1792</context>
<context context-type="linenumber">1788</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -3688,7 +3688,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1362</context>
<context context-type="linenumber">1358</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -3800,7 +3800,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1845</context>
<context context-type="linenumber">1841</context>
</context-group>
</trans-unit>
<trans-unit id="6661109599266152398" datatype="html">
@@ -3811,7 +3811,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1846</context>
<context context-type="linenumber">1842</context>
</context-group>
</trans-unit>
<trans-unit id="5162686434580248853" datatype="html">
@@ -3822,7 +3822,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1847</context>
<context context-type="linenumber">1843</context>
</context-group>
</trans-unit>
<trans-unit id="8157388568390631653" datatype="html">
@@ -5724,7 +5724,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1366</context>
<context context-type="linenumber">1362</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -7966,88 +7966,88 @@
<source>Enter Password</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">513</context>
<context context-type="linenumber">512</context>
</context-group>
</trans-unit>
<trans-unit id="5758784066858623886" datatype="html">
<source>Error retrieving metadata</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">424</context>
<context context-type="linenumber">423</context>
</context-group>
</trans-unit>
<trans-unit id="2218903673684131427" datatype="html">
<source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">526,528</context>
<context context-type="linenumber">525,527</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">986,988</context>
<context context-type="linenumber">982,984</context>
</context-group>
</trans-unit>
<trans-unit id="6357361810318120957" datatype="html">
<source>Document was updated</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">652</context>
<context context-type="linenumber">648</context>
</context-group>
</trans-unit>
<trans-unit id="5154064822428631306" datatype="html">
<source>Document was updated at <x id="PH" equiv-text="formattedModified"/>.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">653</context>
<context context-type="linenumber">649</context>
</context-group>
</trans-unit>
<trans-unit id="8462497568316256794" datatype="html">
<source>Reload to discard your local unsaved edits and load the latest remote version.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">654</context>
<context context-type="linenumber">650</context>
</context-group>
</trans-unit>
<trans-unit id="7967484035994732534" datatype="html">
<source>Reload</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">656</context>
<context context-type="linenumber">652</context>
</context-group>
</trans-unit>
<trans-unit id="2907037627372942104" datatype="html">
<source>Document reloaded with latest changes.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">712</context>
<context context-type="linenumber">708</context>
</context-group>
</trans-unit>
<trans-unit id="6435639868943916539" datatype="html">
<source>Document reloaded.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">723</context>
<context context-type="linenumber">719</context>
</context-group>
</trans-unit>
<trans-unit id="6142395741265832184" datatype="html">
<source>Next document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">825</context>
<context context-type="linenumber">821</context>
</context-group>
</trans-unit>
<trans-unit id="651985345816518480" datatype="html">
<source>Previous document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">835</context>
<context context-type="linenumber">831</context>
</context-group>
</trans-unit>
<trans-unit id="2885986061416655600" datatype="html">
<source>Close document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">843</context>
<context context-type="linenumber">839</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
@@ -8058,67 +8058,67 @@
<source>Save document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">850</context>
<context context-type="linenumber">846</context>
</context-group>
</trans-unit>
<trans-unit id="1784543155727940353" datatype="html">
<source>Save and close / next</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">859</context>
<context context-type="linenumber">855</context>
</context-group>
</trans-unit>
<trans-unit id="7427704425579737895" datatype="html">
<source>Error retrieving version content</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">969</context>
<context context-type="linenumber">965</context>
</context-group>
</trans-unit>
<trans-unit id="3456881259945295697" datatype="html">
<source>Error retrieving suggestions.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1030</context>
<context context-type="linenumber">1026</context>
</context-group>
</trans-unit>
<trans-unit id="2194092841814123758" datatype="html">
<source>Document &quot;<x id="PH" equiv-text="newValues.title"/>&quot; saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1242</context>
<context context-type="linenumber">1238</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1269</context>
<context context-type="linenumber">1265</context>
</context-group>
</trans-unit>
<trans-unit id="6626387786259219838" datatype="html">
<source>Error saving document &quot;<x id="PH" equiv-text="this.document().title"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1275</context>
<context context-type="linenumber">1271</context>
</context-group>
</trans-unit>
<trans-unit id="448882439049417053" datatype="html">
<source>Error saving document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1330</context>
<context context-type="linenumber">1326</context>
</context-group>
</trans-unit>
<trans-unit id="8410796510716511826" datatype="html">
<source>Do you really want to move the document &quot;<x id="PH" equiv-text="this.document().title"/>&quot; to the trash?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1363</context>
<context context-type="linenumber">1359</context>
</context-group>
</trans-unit>
<trans-unit id="282586936710748252" datatype="html">
<source>Documents can be restored prior to permanent deletion.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1364</context>
<context context-type="linenumber">1360</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -8129,14 +8129,14 @@
<source>Error deleting document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1385</context>
<context context-type="linenumber">1381</context>
</context-group>
</trans-unit>
<trans-unit id="619486176823357521" datatype="html">
<source>Reprocess confirm</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1405</context>
<context context-type="linenumber">1401</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -8147,102 +8147,102 @@
<source>This operation will permanently recreate the archive file for this document.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1406</context>
<context context-type="linenumber">1402</context>
</context-group>
</trans-unit>
<trans-unit id="302054111564709516" datatype="html">
<source>The archive file will be re-generated with the current settings.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1407</context>
<context context-type="linenumber">1403</context>
</context-group>
</trans-unit>
<trans-unit id="4700389117298802932" datatype="html">
<source>Reprocess operation for &quot;<x id="PH" equiv-text="this.document().title"/>&quot; will begin in the background.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1417</context>
<context context-type="linenumber">1413</context>
</context-group>
</trans-unit>
<trans-unit id="4409560272830824468" datatype="html">
<source>Error executing operation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1428</context>
<context context-type="linenumber">1424</context>
</context-group>
</trans-unit>
<trans-unit id="6030453331794586802" datatype="html">
<source>Error downloading document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1491</context>
<context context-type="linenumber">1487</context>
</context-group>
</trans-unit>
<trans-unit id="4458954481601077369" datatype="html">
<source>Page Fit</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1569</context>
<context context-type="linenumber">1565</context>
</context-group>
</trans-unit>
<trans-unit id="4663705961777238777" datatype="html">
<source>PDF edit operation for &quot;<x id="PH" equiv-text="this.document().title"/>&quot; will begin in the background.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1812</context>
<context context-type="linenumber">1808</context>
</context-group>
</trans-unit>
<trans-unit id="9043972994040261999" datatype="html">
<source>Error executing PDF edit operation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1824</context>
<context context-type="linenumber">1820</context>
</context-group>
</trans-unit>
<trans-unit id="6172690334763056188" datatype="html">
<source>Please enter the current password before attempting to remove it.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1835</context>
<context context-type="linenumber">1831</context>
</context-group>
</trans-unit>
<trans-unit id="968660764814228922" datatype="html">
<source>Password removal operation for &quot;<x id="PH" equiv-text="this.document().title"/>&quot; will begin in the background.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1869</context>
<context context-type="linenumber">1865</context>
</context-group>
</trans-unit>
<trans-unit id="2282118435712883014" datatype="html">
<source>Error executing password removal operation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1883</context>
<context context-type="linenumber">1879</context>
</context-group>
</trans-unit>
<trans-unit id="3740891324955700797" datatype="html">
<source>Print failed.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1929</context>
<context context-type="linenumber">1925</context>
</context-group>
</trans-unit>
<trans-unit id="6457245677384603573" datatype="html">
<source>Error loading document for printing.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1942</context>
<context context-type="linenumber">1938</context>
</context-group>
</trans-unit>
<trans-unit id="6085793215710522488" datatype="html">
<source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">2012</context>
<context context-type="linenumber">2008</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">2018</context>
<context context-type="linenumber">2014</context>
</context-group>
</trans-unit>
<trans-unit id="4958946940233632319" datatype="html">
-3
View File
@@ -2,9 +2,6 @@ packages:
- "."
minimumReleaseAge: 10080
trustPolicy: no-downgrade
trustPolicyExclude:
- "chokidar@4.0.3"
- "semver@6.3.1 || 5.7.2"
allowBuilds:
"@parcel/watcher": true
canvas: true
@@ -280,52 +280,10 @@ main {
}
}
@media screen and (max-width: 575.98px) {
.navbar {
display: grid;
grid-template-columns: 1fr auto 1fr;
grid-template-areas:
"toggler brand actions"
"search search search";
}
@media screen and (min-width: 376px) and (max-width: 768px) {
.navbar-toggler {
grid-area: toggler;
text-align: left;
}
.navbar-brand {
grid-area: brand;
min-width: 0;
max-width: 100%;
justify-self: center;
overflow: hidden;
> div,
.custom-title {
min-width: 0;
max-width: 100%;
overflow: hidden;
}
.title,
.byline {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.search-container {
grid-area: search;
width: 100%;
}
.navbar > ul {
grid-area: actions;
justify-self: end;
flex-wrap: nowrap;
// compensate for 2 buttons on the right
margin-right: 45px;
}
}
@@ -335,6 +293,13 @@ main {
}
}
@media screen and (max-width: 345px) {
.custom-title {
max-width: 110px;
overflow: hidden;
}
}
:host ::ng-deep .dropdown.show .dropdown-toggle,
:host ::ng-deep .dropdown-toggle:hover {
opacity: 0.7;
@@ -283,22 +283,6 @@ describe('PngxPdfViewerComponent', () => {
expect(mockViewer.currentPageNumber).toBe(1)
})
it('reloads when the source revision changes', () => {
const resetSpy = jest.spyOn(component as any, 'resetViewerState')
const loadSpy = jest
.spyOn(component as any, 'loadDocument')
.mockImplementation(() => {})
component.src = 'test.pdf'
component.sourceRevision = 1
component.ngOnChanges({
sourceRevision: new SimpleChange(0, 1, false),
})
expect(resetSpy).toHaveBeenCalled()
expect(loadSpy).toHaveBeenCalled()
})
it('applies viewer state after view init when already loaded', () => {
const applySpy = jest.spyOn(component as any, 'applyViewerState')
;(component as any).hasLoaded = true
@@ -43,7 +43,6 @@ export class PngxPdfViewerComponent
private readonly document = inject<Document>(DOCUMENT)
@Input() src!: string
@Input() sourceRevision = 0
@Input() password?: string
@Input() page?: number
@Output() pageChange = new EventEmitter<number>()
@@ -94,7 +93,7 @@ export class PngxPdfViewerComponent
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['src'] || changes['sourceRevision'] || changes['password']) {
if (changes['src'] || changes['password']) {
this.resetViewerState()
if (this.src) {
this.loadDocument()
@@ -472,7 +472,6 @@
<div class="preview-sticky pdf-viewer-container">
<pngx-pdf-viewer
[src]="pdfSource()"
[sourceRevision]="previewRevision()"
[password]="pdfPassword()"
[renderMode]="PdfRenderMode.All"
[page]="previewCurrentPage()" (pageChange)="previewCurrentPage.set($event)"
@@ -1602,7 +1602,6 @@ describe('DocumentDetailComponent', () => {
expect(openDoc.__changedFields).toEqual([])
expect(setDirtySpy).toHaveBeenCalledWith(openDoc, false)
expect(saveSpy).toHaveBeenCalled()
expect(component.previewRevision()).toBe(1)
})
it('should ignore incoming update for a different document id', () => {
@@ -249,7 +249,6 @@ export class DocumentDetailComponent
titleSubject: Subject<string> = new Subject()
readonly previewUrl = signal<string>(undefined)
readonly pdfSource = signal<string>(undefined)
readonly previewRevision = signal(0)
readonly pdfPassword = signal<string>(undefined)
readonly thumbUrl = signal<string>(undefined)
readonly previewText = signal<string>(undefined)
@@ -610,9 +609,6 @@ export class DocumentDetailComponent
.subscribe()
}
this.updateComponent(useDoc)
if (forceRemote) {
this.previewRevision.update((revision) => revision + 1)
}
this.titleSubject
.pipe(
debounceTime(1000),
@@ -25,7 +25,7 @@
<input #textFilterInput class="form-control form-control-sm" type="text"
[disabled]="textFilterModifierIsNull"
[(ngModel)]="textFilter"
(keydown)="textFilterKeydown($event)"
(keyup)="textFilterKeyup($event)"
[ngbTypeahead]="searchAutoComplete"
(selectItem)="itemSelected($event)"
[readonly]="textFilterTarget === 'fulltext-morelike'">
@@ -2180,17 +2180,17 @@ describe('FilterEditorComponent', () => {
it('should support Enter / Esc key on text field', () => {
component.textFilterInput.nativeElement.value = 'foo'
component.textFilterInput.nativeElement.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Enter' })
new KeyboardEvent('keyup', { key: 'Enter' })
)
expect(component.textFilter).toEqual('foo')
component.textFilterInput.nativeElement.value = 'foo bar'
component.textFilterInput.nativeElement.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape' })
new KeyboardEvent('keyup', { key: 'Escape' })
)
expect(component.textFilter).toEqual('')
const blurSpy = jest.spyOn(component.textFilterInput.nativeElement, 'blur')
component.textFilterInput.nativeElement.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape' })
new KeyboardEvent('keyup', { key: 'Escape' })
)
expect(blurSpy).toHaveBeenCalled()
})
@@ -1312,7 +1312,7 @@ export class FilterEditorComponent
}
}
textFilterKeydown(event: KeyboardEvent) {
textFilterKeyup(event: KeyboardEvent) {
if (event.key == 'Enter') {
const filterString = (
this.textFilterInput.nativeElement as HTMLInputElement
@@ -108,18 +108,6 @@ describe('PermissionsService', () => {
actionKey: 'View', // PermissionAction.View
typeKey: 'Document', // PermissionType.Document
})
expect(
permissionsService.getPermissionKeys('view_global_statistics')
).toEqual({
actionKey: 'View', // PermissionAction.View
typeKey: 'GlobalStatistics', // PermissionType.GlobalStatistics
})
expect(
permissionsService.getPermissionKeys('view_system_monitoring')
).toEqual({
actionKey: 'View', // PermissionAction.View
typeKey: 'SystemMonitoring', // PermissionType.SystemMonitoring
})
})
it('correctly checks explicit global permissions', () => {
+10 -7
View File
@@ -110,16 +110,19 @@ export class PermissionsService {
actionKey: string
typeKey: string
} {
const matches = permissionStr.match(/(.+)_/)
let typeKey
let actionKey
const actionIndex = Object.values(PermissionAction).findIndex((action) =>
permissionStr.startsWith(`${action}_`)
)
if (actionIndex > -1) {
const action = Object.values(PermissionAction)[actionIndex]
actionKey = Object.keys(PermissionAction)[actionIndex]
if (matches?.length > 0) {
const action = matches[1]
const actionIndex = Object.values(PermissionAction).indexOf(
action as PermissionAction
)
if (actionIndex > -1) {
actionKey = Object.keys(PermissionAction)[actionIndex]
}
const typeIndex = Object.values(PermissionType).indexOf(
permissionStr.replace(`${action}_`, '%s_') as PermissionType
permissionStr.replace(action, '%s') as PermissionType
)
if (typeIndex > -1) {
typeKey = Object.keys(PermissionType)[typeIndex]
+4 -4
View File
@@ -1996,7 +1996,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">15</context>
</context-group>
<target state="translated">Descarta-ho tot</target>
<target state="needs-translation">Dismiss all</target>
</trans-unit>
<trans-unit id="1616102757855967475" datatype="html">
<source>All</source>
@@ -2076,7 +2076,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">96</context>
</context-group>
<target state="translated">Cerca tasques</target>
<target state="needs-translation">Search tasks</target>
</trans-unit>
<trans-unit id="6849725902312323996" datatype="html">
<source>Reset filters</source>
@@ -2696,7 +2696,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">348</context>
</context-group>
<target state="translated">Confirma Descarta Tot</target>
<target state="needs-translation">Confirm Dismiss All</target>
</trans-unit>
<trans-unit id="4157200209636243740" datatype="html">
<source>Dismiss all <x id="PH" equiv-text="this.totalTasks()"/> tasks?</source>
@@ -2704,7 +2704,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">349</context>
</context-group>
<target state="translated"/>
<target state="needs-translation">Dismiss all <x id="PH" equiv-text="this.totalTasks()"/> tasks?</target>
</trans-unit>
<trans-unit id="8149502458056418229" datatype="html">
<source>Success. New document id <x id="PH" equiv-text="documentId"/> created</source>
+87 -87
View File
@@ -828,7 +828,7 @@
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
<target state="translated">řádků</target>
<target state="translated">řádky</target>
</trans-unit>
<trans-unit id="8838884664569764142" datatype="html">
<source>Auto refresh</source>
@@ -1352,7 +1352,7 @@
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
<context context-type="linenumber">248</context>
</context-group>
<target state="translated">Vestavěná pole k zobrazení:</target>
<target state="needs-translation">Built-in fields to show:</target>
</trans-unit>
<trans-unit id="3467966318201103991" datatype="html">
<source>Uncheck fields to hide them on the document details page.</source>
@@ -1360,7 +1360,7 @@
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
<context context-type="linenumber">260</context>
</context-group>
<target state="translated">Zrušte zaškrtnutí polí pro jejich skrytí na stránce s podrobnostmi o dokumentu.</target>
<target state="needs-translation">Uncheck fields to hide them on the document details page.</target>
</trans-unit>
<trans-unit id="8508424367627989968" datatype="html" approved="yes">
<source>Bulk editing</source>
@@ -1400,7 +1400,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1787</context>
</context-group>
<target state="translated">Editor PDF</target>
<target state="translated">PDF editor</target>
</trans-unit>
<trans-unit id="1577733187050997705" datatype="html">
<source>Default editing mode</source>
@@ -1964,7 +1964,7 @@
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">298</context>
</context-group>
<target state="translated">Úlohy</target>
<target state="needs-translation">Tasks</target>
</trans-unit>
<trans-unit id="8492095365580052820" datatype="html">
<source>Tasks shows detailed information about document consumption and system tasks.</source>
@@ -1972,7 +1972,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<target state="translated">Úlohy zobrazují podrobné informace o zpracování dokumentů a systémových úlohách.</target>
<target state="needs-translation">Tasks shows detailed information about document consumption and system tasks.</target>
</trans-unit>
<trans-unit id="103921551219467537" datatype="html">
<source>Clear selection</source>
@@ -1996,7 +1996,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">15</context>
</context-group>
<target state="translated">Zahodit vše</target>
<target state="needs-translation">Dismiss all</target>
</trans-unit>
<trans-unit id="1616102757855967475" datatype="html" approved="yes">
<source>All</source>
@@ -2056,7 +2056,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">215</context>
</context-group>
<target state="translated">Všechny typy</target>
<target state="needs-translation">All types</target>
</trans-unit>
<trans-unit id="131016739441837046" datatype="html">
<source>All sources</source>
@@ -2068,7 +2068,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">227</context>
</context-group>
<target state="translated">Všechny zdroje</target>
<target state="needs-translation">All sources</target>
</trans-unit>
<trans-unit id="3570013039254960445" datatype="html">
<source>Search tasks</source>
@@ -2100,7 +2100,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">126</context>
</context-group>
<target state="translated">{VAR_PLURAL, plural, few {<x id="INTERPOLATION"/> úlohy} many {<x id="INTERPOLATION"/> úloh}=1 {1 úloha} other {<x id="INTERPOLATION"/> úloh}}</target>
<target state="needs-translation">{VAR_PLURAL, plural, =1 {1 task} other {<x id="INTERPOLATION"/> tasks}}</target>
</trans-unit>
<trans-unit id="8953033926734869941" datatype="html" approved="yes">
<source>Name</source>
@@ -2444,7 +2444,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">289</context>
</context-group>
<target state="translated">Zvoleným filtrům neodpovídají žádné úlohy.</target>
<target state="needs-translation">No tasks match the current filters.</target>
</trans-unit>
<trans-unit id="2525230676386818985" datatype="html">
<source>Result</source>
@@ -2460,7 +2460,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">55</context>
</context-group>
<target state="translated">Vyžaduje pozornost</target>
<target state="needs-translation">Needs attention</target>
</trans-unit>
<trans-unit id="3307214558906712688" datatype="html">
<source>In progress</source>
@@ -2468,7 +2468,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">56</context>
</context-group>
<target state="translated">Probíhá</target>
<target state="needs-translation">In progress</target>
</trans-unit>
<trans-unit id="1371178633976505412" datatype="html">
<source>Recently completed</source>
@@ -2476,7 +2476,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">57</context>
</context-group>
<target state="translated">Nedávno dokončené</target>
<target state="needs-translation">Recently completed</target>
</trans-unit>
<trans-unit id="2936452645673957909" datatype="html">
<source>Consume File</source>
@@ -2484,7 +2484,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">66</context>
</context-group>
<target state="translated">Zpracování souboru</target>
<target state="needs-translation">Consume File</target>
</trans-unit>
<trans-unit id="5622386857946132209" datatype="html">
<source>Train Classifier</source>
@@ -2492,7 +2492,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">70</context>
</context-group>
<target state="translated">Trénování klasifikátoru</target>
<target state="needs-translation">Train Classifier</target>
</trans-unit>
<trans-unit id="776309955507105337" datatype="html">
<source>Sanity Check</source>
@@ -2500,7 +2500,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">74</context>
</context-group>
<target state="translated">Kontrola zdraví</target>
<target state="needs-translation">Sanity Check</target>
</trans-unit>
<trans-unit id="7502272564743467653" datatype="html">
<source>Mail Fetch</source>
@@ -2520,7 +2520,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">77</context>
</context-group>
<target state="translated">Index LLM</target>
<target state="needs-translation">LLM Index</target>
</trans-unit>
<trans-unit id="6402092370576716734" datatype="html">
<source>Empty Trash</source>
@@ -2528,7 +2528,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">80</context>
</context-group>
<target state="translated">Vyprázdnění koše</target>
<target state="needs-translation">Empty Trash</target>
</trans-unit>
<trans-unit id="4173754487295857311" datatype="html">
<source>Check Workflows</source>
@@ -2536,7 +2536,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">84</context>
</context-group>
<target state="translated">Kontrola postupů</target>
<target state="needs-translation">Check Workflows</target>
</trans-unit>
<trans-unit id="2065831805515896240" datatype="html">
<source>Bulk Update</source>
@@ -2544,7 +2544,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">88</context>
</context-group>
<target state="translated">Hromadná aktualizace</target>
<target state="needs-translation">Bulk Update</target>
</trans-unit>
<trans-unit id="5062348912423871240" datatype="html">
<source>Reprocess Document</source>
@@ -2552,7 +2552,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">92</context>
</context-group>
<target state="translated">Opětovné zpracování dokumentu</target>
<target state="needs-translation">Reprocess Document</target>
</trans-unit>
<trans-unit id="6903696804811148799" datatype="html">
<source>Build Share Link</source>
@@ -2560,7 +2560,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">96</context>
</context-group>
<target state="translated">Sestavení odkazu ke sdílení</target>
<target state="needs-translation">Build Share Link</target>
</trans-unit>
<trans-unit id="6581973658756561124" datatype="html">
<source>Bulk Delete</source>
@@ -2568,7 +2568,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">100</context>
</context-group>
<target state="translated">Hromadné smazání</target>
<target state="needs-translation">Bulk Delete</target>
</trans-unit>
<trans-unit id="9172233176401579786" datatype="html">
<source>Scheduled</source>
@@ -2612,7 +2612,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">119</context>
</context-group>
<target state="translated">Zpracování složky</target>
<target state="needs-translation">Folder Consume</target>
</trans-unit>
<trans-unit id="1456901843819961615" datatype="html">
<source>Email Consume</source>
@@ -2620,7 +2620,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">123</context>
</context-group>
<target state="translated">Zpracování e-mailu</target>
<target state="needs-translation">Email Consume</target>
</trans-unit>
<trans-unit id="29832309535656200" datatype="html">
<source>System</source>
@@ -2628,7 +2628,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">125</context>
</context-group>
<target state="translated">Systém</target>
<target state="needs-translation">System</target>
</trans-unit>
<trans-unit id="2233560223291461480" datatype="html">
<source>Manual</source>
@@ -2636,7 +2636,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">126</context>
</context-group>
<target state="translated">Ruční</target>
<target state="needs-translation">Manual</target>
</trans-unit>
<trans-unit id="5404910960991552159" datatype="html">
<source>Dismiss selected</source>
@@ -2652,7 +2652,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">240</context>
</context-group>
<target state="translated">Zahodit viditelné</target>
<target state="needs-translation">Dismiss visible</target>
</trans-unit>
<trans-unit id="3169751690815214293" datatype="html">
<source>Confirm Dismiss</source>
@@ -2696,7 +2696,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">348</context>
</context-group>
<target state="translated">Potvrdit zahození všech</target>
<target state="needs-translation">Confirm Dismiss All</target>
</trans-unit>
<trans-unit id="4157200209636243740" datatype="html">
<source>Dismiss all <x id="PH" equiv-text="this.totalTasks()"/> tasks?</source>
@@ -2704,7 +2704,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">349</context>
</context-group>
<target state="translated">Zahodit všech <x id="PH" equiv-text="this.totalTasks()"/> úloh?</target>
<target state="needs-translation">Dismiss all <x id="PH" equiv-text="this.totalTasks()"/> tasks?</target>
</trans-unit>
<trans-unit id="8149502458056418229" datatype="html">
<source>Success. New document id <x id="PH" equiv-text="documentId"/> created</source>
@@ -2712,7 +2712,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">408</context>
</context-group>
<target state="translated">Úspěch. Vytvořen nový dokument s ID <x id="PH" equiv-text="documentId"/></target>
<target state="needs-translation">Success. New document id <x id="PH" equiv-text="documentId"/> created</target>
</trans-unit>
<trans-unit id="8760066891202884337" datatype="html">
<source>Duplicate of document #<x id="PH" equiv-text="duplicateOf"/></source>
@@ -3540,7 +3540,7 @@
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">183</context>
</context-group>
<target state="translated">Atributy</target>
<target state="needs-translation">Attributes</target>
</trans-unit>
<trans-unit id="7437910965833684826" datatype="html" approved="yes">
<source>Correspondents</source>
@@ -3572,7 +3572,7 @@
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context>
<context context-type="linenumber">105</context>
</context-group>
<target state="translated">Typy dokumentů</target>
<target state="needs-translation">Document types</target>
</trans-unit>
<trans-unit id="8835528846812581148" datatype="html">
<source>Storage paths</source>
@@ -3584,7 +3584,7 @@
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context>
<context context-type="linenumber">115</context>
</context-group>
<target state="translated">Cesty úložiště</target>
<target state="needs-translation">Storage paths</target>
</trans-unit>
<trans-unit id="9149498548977462220" datatype="html">
<source>Custom fields</source>
@@ -5540,7 +5540,7 @@
<context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
<target state="translated">Přístup ke stavu systému, protokolům, backendu Django</target>
<target state="needs-translation">Access system status, logs, Django backend</target>
</trans-unit>
<trans-unit id="1808271538028523977" datatype="html">
<source>Superuser</source>
@@ -5556,7 +5556,7 @@
<context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html</context>
<context context-type="linenumber">30</context>
</context-group>
<target state="translated">Uděluje všechna oprávnění a může zobrazovat všechny objekty</target>
<target state="needs-translation">Grants all permissions and can view all objects</target>
</trans-unit>
<trans-unit id="8900662509426586619" datatype="html">
<source>Two-factor Authentication</source>
@@ -6344,7 +6344,7 @@
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">271</context>
</context-group>
<target state="translated">Má kterýkoli z následujících typů dokumentů</target>
<target state="needs-translation">Has any of these document types</target>
</trans-unit>
<trans-unit id="1507843981661822403" datatype="html">
<source>Does not have document types</source>
@@ -7546,7 +7546,7 @@
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">116</context>
</context-group>
<target state="translated">Spravovat balíčky sdílení odkazů</target>
<target state="needs-translation">Manage share link bundles</target>
</trans-unit>
<trans-unit id="1070687661569746428" datatype="html">
<source>Create share link bundle</source>
@@ -7554,7 +7554,7 @@
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.ts</context>
<context context-type="linenumber">61</context>
</context-group>
<target state="translated">Vytvořit balíček sdílení odkazů</target>
<target state="needs-translation">Create share link bundle</target>
</trans-unit>
<trans-unit id="2362798555008696742" datatype="html">
<source>Create link</source>
@@ -7590,7 +7590,7 @@
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
<context context-type="linenumber">21</context>
</context-group>
<target state="translated">Aktualizace stavu každých pár sekund během přípravy balíčků.</target>
<target state="needs-translation">Status updates every few seconds while bundles are being prepared.</target>
</trans-unit>
<trans-unit id="5194711788228004418" datatype="html">
<source>No share link bundles currently exist.</source>
@@ -7598,7 +7598,7 @@
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
<context context-type="linenumber">25</context>
</context-group>
<target state="translated">V současné době neexistují žádné balíčky.</target>
<target state="needs-translation">No share link bundles currently exist.</target>
</trans-unit>
<trans-unit id="2841249062294860035" datatype="html">
<source>Built:</source>
@@ -7650,7 +7650,7 @@
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.ts</context>
<context context-type="linenumber">42</context>
</context-group>
<target state="translated">Balíčky sdílení odkazů</target>
<target state="needs-translation">Share link bundles</target>
</trans-unit>
<trans-unit id="1541995860059283227" datatype="html">
<source>Failed to load share link bundles.</source>
@@ -7658,7 +7658,7 @@
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
<target state="translated">Nepodařilo se načíst balíčky sdílení odkazů.</target>
<target state="needs-translation">Failed to load share link bundles.</target>
</trans-unit>
<trans-unit id="5417797597565486403" datatype="html">
<source>Error retrieving share link bundles.</source>
@@ -7942,7 +7942,7 @@
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">147</context>
</context-group>
<target state="translated">Nedávná aktivita úloh <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;small text-muted fw-light&quot;&gt;"/>(<x id="INTERPOLATION" equiv-text="{{status().tasks.summary.days}}"/> days)<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
<target state="needs-translation">Recent Task Activity <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;small text-muted fw-light&quot;&gt;"/>(<x id="INTERPOLATION" equiv-text="{{status().tasks.summary.days}}"/> days)<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
</trans-unit>
<trans-unit id="3448462145758383019" datatype="html">
<source>Total</source>
@@ -7950,7 +7950,7 @@
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">152</context>
</context-group>
<target state="translated">Celkem</target>
<target state="needs-translation">Total</target>
</trans-unit>
<trans-unit id="3521084103654700903" datatype="html">
<source>Successful</source>
@@ -7958,7 +7958,7 @@
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">156</context>
</context-group>
<target state="translated">Úspěšných</target>
<target state="needs-translation">Successful</target>
</trans-unit>
<trans-unit id="7256395947475975935" datatype="html">
<source>Failed</source>
@@ -7970,7 +7970,7 @@
<context context-type="sourcefile">src/app/data/share-link-bundle.ts</context>
<context context-type="linenumber">44</context>
</context-group>
<target state="translated">Selhané</target>
<target state="needs-translation">Failed</target>
</trans-unit>
<trans-unit id="4416413576346763682" datatype="html">
<source>Pending</source>
@@ -7982,7 +7982,7 @@
<context context-type="sourcefile">src/app/data/share-link-bundle.ts</context>
<context context-type="linenumber">41</context>
</context-group>
<target state="translated">Čekající</target>
<target state="needs-translation">Pending</target>
</trans-unit>
<trans-unit id="2000433102211963975" datatype="html">
<source>No recent tasks</source>
@@ -7990,7 +7990,7 @@
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">169</context>
</context-group>
<target state="translated">Žádné nedávné úlohy</target>
<target state="needs-translation">No recent tasks</target>
</trans-unit>
<trans-unit id="2041675390931385838" datatype="html">
<source>Health</source>
@@ -8090,7 +8090,7 @@
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">283</context>
</context-group>
<target state="translated">Index AI</target>
<target state="needs-translation">AI Index</target>
</trans-unit>
<trans-unit id="6732151329960766506" datatype="html">
<source>Copy Raw Error</source>
@@ -8660,7 +8660,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">318</context>
</context-group>
<target state="translated">Původní kontrolní součet SHA256</target>
<target state="needs-translation">Original SHA256 checksum</target>
</trans-unit>
<trans-unit id="5888243105821763422" datatype="html" approved="yes">
<source>Original file size</source>
@@ -8684,7 +8684,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">331</context>
</context-group>
<target state="translated">Archivní kontrolní součet SHA256</target>
<target state="needs-translation">Archive SHA256 checksum</target>
</trans-unit>
<trans-unit id="6033581412811562084" datatype="html" approved="yes">
<source>Archive file size</source>
@@ -8716,7 +8716,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">368,371</context>
</context-group>
<target state="translated">Poznámky <x id="START_BLOCK_IF" equiv-text="@if (document()?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></target>
<target state="needs-translation">Notes <x id="START_BLOCK_IF" equiv-text="@if (document()?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></target>
</trans-unit>
<trans-unit id="186236568870281953" datatype="html">
<source>History</source>
@@ -9116,7 +9116,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<target state="translated">Verze</target>
<target state="needs-translation">Versions</target>
</trans-unit>
<trans-unit id="4077520913910941990" datatype="html">
<source>Label</source>
@@ -9124,7 +9124,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">10</context>
</context-group>
<target state="translated">Označení</target>
<target state="needs-translation">Label</target>
</trans-unit>
<trans-unit id="3915966149686975421" datatype="html">
<source>Optional</source>
@@ -9132,7 +9132,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">17</context>
</context-group>
<target state="translated">Volitelné</target>
<target state="needs-translation">Optional</target>
</trans-unit>
<trans-unit id="787787001194154763" datatype="html">
<source>Add new version</source>
@@ -9140,7 +9140,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">32</context>
</context-group>
<target state="translated">Přidat novou verzi</target>
<target state="needs-translation">Add new version</target>
</trans-unit>
<trans-unit id="3119565892291077820" datatype="html">
<source>Uploading version...</source>
@@ -9164,7 +9164,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
<target state="translated">Nepodařilo se nahrát verzi.</target>
<target state="needs-translation">Version upload failed.</target>
</trans-unit>
<trans-unit id="6328986243040954523" datatype="html">
<source>Version label</source>
@@ -9176,7 +9176,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">85</context>
</context-group>
<target state="translated">Označení verze</target>
<target state="needs-translation">Version label</target>
</trans-unit>
<trans-unit id="2724055831234181057" datatype="html">
<source>Version</source>
@@ -9184,7 +9184,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
<context context-type="linenumber">96</context>
</context-group>
<target state="translated">Verze</target>
<target state="needs-translation">Version</target>
</trans-unit>
<trans-unit id="2491751767883178298" datatype="html">
<source>Delete this version?</source>
@@ -9216,7 +9216,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.ts</context>
<context context-type="linenumber">189</context>
</context-group>
<target state="translated">Chyba při aktualizaci označení verze</target>
<target state="needs-translation">Error updating version label</target>
</trans-unit>
<trans-unit id="3567158019134759286" datatype="html">
<source>Uploading new version. Processing will happen in the background.</source>
@@ -9244,7 +9244,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.ts</context>
<context context-type="linenumber">271</context>
</context-group>
<target state="translated">Nahrání selhalo.</target>
<target state="needs-translation">Upload failed.</target>
</trans-unit>
<trans-unit id="8350861980330256599" datatype="html">
<source>Error uploading new version</source>
@@ -9348,7 +9348,7 @@
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">113</context>
</context-group>
<target state="translated">Vytvořit balíček sdílení odkazů</target>
<target state="needs-translation">Create a share link bundle</target>
</trans-unit>
<trans-unit id="1015374532025907183" datatype="html">
<source>Include:</source>
@@ -9755,7 +9755,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">95,96</context>
</context-group>
<target state="translated">Vytvořeno: <x id="INTERPOLATION" equiv-text="{{ document().created | customDate }}"/></target>
<target state="needs-translation">Created: <x id="INTERPOLATION" equiv-text="{{ document().created | customDate }}"/></target>
</trans-unit>
<trans-unit id="2030261243264601523" datatype="html">
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document().added | customDate }}"/></source>
@@ -9771,7 +9771,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">96,97</context>
</context-group>
<target state="translated">Přidáno: <x id="INTERPOLATION" equiv-text="{{ document().added | customDate }}"/></target>
<target state="needs-translation">Added: <x id="INTERPOLATION" equiv-text="{{ document().added | customDate }}"/></target>
</trans-unit>
<trans-unit id="4235671847487610290" datatype="html">
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document().modified | customDate }}"/></source>
@@ -10247,7 +10247,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">497</context>
</context-group>
<target state="translated">Pohled „<x id="PH" equiv-text="savedView.name"/>“ úspěšně vytvořen, nepodařilo se ale aktualizovat nastavení viditelnosti.</target>
<target state="needs-translation">View "<x id="PH" equiv-text="savedView.name"/>" created successfully, but could not update visibility settings.</target>
</trans-unit>
<trans-unit id="739880801667335279" datatype="html">
<source>Dates</source>
@@ -11367,7 +11367,7 @@
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
<context context-type="linenumber">35</context>
</context-group>
<target state="translated"><x id="START_TAG_I_BS" ctype="x-i_bs" equiv-text="bs class=&quot;me-1&quot; name=&quot;person-fill-lock&quot;&gt;"/><x id="CLOSE_TAG_I_BS" ctype="x-i_bs" equiv-text="Oprávnění&lt;"/>Oprávnění</target>
<target state="needs-translation"><x id="START_TAG_I_BS" ctype="x-i_bs" equiv-text="bs class=&quot;me-1&quot; name=&quot;person-fill-lock&quot;&gt;"/><x id="CLOSE_TAG_I_BS" ctype="x-i_bs" equiv-text="Permissions&lt;"/>Permissions</target>
</trans-unit>
<trans-unit id="6338800642797811873" datatype="html">
<source>Documents page size</source>
@@ -11415,7 +11415,7 @@
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
<context context-type="linenumber">62</context>
</context-group>
<target state="translated">Upozornění: pořadí nebude zachováno</target>
<target state="needs-translation">Note: ordering is not preserved</target>
</trans-unit>
<trans-unit id="7877440816920439876" datatype="html" approved="yes">
<source>No saved views defined.</source>
@@ -11864,7 +11864,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">54</context>
</context-group>
<target state="translated">Nastavení AI</target>
<target state="needs-translation">AI Settings</target>
</trans-unit>
<trans-unit id="1313137480169642057" datatype="html">
<source>Output Type</source>
@@ -11896,7 +11896,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">120</context>
</context-group>
<target state="translated">Generování archivních souborů</target>
<target state="needs-translation">Archive File Generation</target>
</trans-unit>
<trans-unit id="1115402553541327390" datatype="html">
<source>Image DPI</source>
@@ -12072,7 +12072,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">277</context>
</context-group>
<target state="translated">Rozdělení podle čárových kódů štítků</target>
<target state="needs-translation">Split on Tag Barcodes</target>
</trans-unit>
<trans-unit id="7011909364081812031" datatype="html">
<source>AI Enabled</source>
@@ -12080,7 +12080,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">284</context>
</context-group>
<target state="translated">AI povoleno</target>
<target state="needs-translation">AI Enabled</target>
</trans-unit>
<trans-unit id="8028880048909383956" datatype="html">
<source>Consider privacy implications when enabling AI features, especially if using a remote model.</source>
@@ -12088,7 +12088,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">288</context>
</context-group>
<target state="translated">Při aktivaci funkcí umělé inteligence zvažte dopady na ochranu osobních údajů, zejména pokud používáte vzdálený model.</target>
<target state="needs-translation">Consider privacy implications when enabling AI features, especially if using a remote model.</target>
</trans-unit>
<trans-unit id="8131374115579345652" datatype="html">
<source>LLM Embedding Backend</source>
@@ -12096,7 +12096,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">292</context>
</context-group>
<target state="translated">Backend pro vkládání LLM</target>
<target state="needs-translation">LLM Embedding Backend</target>
</trans-unit>
<trans-unit id="6647708571891295756" datatype="html">
<source>LLM Embedding Model</source>
@@ -12104,7 +12104,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">300</context>
</context-group>
<target state="translated">Model vkládání LLM</target>
<target state="needs-translation">LLM Embedding Model</target>
</trans-unit>
<trans-unit id="3554114880473286122" datatype="html">
<source>LLM Embedding Endpoint</source>
@@ -12112,7 +12112,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">307</context>
</context-group>
<target state="translated">Koncový bod vkládání LLM</target>
<target state="needs-translation">LLM Embedding Endpoint</target>
</trans-unit>
<trans-unit id="1044242175651289991" datatype="html">
<source>LLM Embedding Chunk Size</source>
@@ -12120,7 +12120,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">314</context>
</context-group>
<target state="translated">Velikost bloku vkládání LLM</target>
<target state="needs-translation">LLM Embedding Chunk Size</target>
</trans-unit>
<trans-unit id="7218245223139363113" datatype="html">
<source>LLM Context Size</source>
@@ -12128,7 +12128,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">321</context>
</context-group>
<target state="translated">Velikost kontextu LLM</target>
<target state="needs-translation">LLM Context Size</target>
</trans-unit>
<trans-unit id="4234495692726214397" datatype="html">
<source>LLM Backend</source>
@@ -12136,7 +12136,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">328</context>
</context-group>
<target state="translated">Backend LLM</target>
<target state="needs-translation">LLM Backend</target>
</trans-unit>
<trans-unit id="7935234833834000002" datatype="html">
<source>LLM Model</source>
@@ -12144,7 +12144,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">336</context>
</context-group>
<target state="translated">Model LLM</target>
<target state="needs-translation">LLM Model</target>
</trans-unit>
<trans-unit id="1980550530387803165" datatype="html">
<source>LLM API Key</source>
@@ -12152,7 +12152,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">343</context>
</context-group>
<target state="translated">Klíč API LLM</target>
<target state="needs-translation">LLM API Key</target>
</trans-unit>
<trans-unit id="6126617860376156501" datatype="html">
<source>LLM Endpoint</source>
@@ -12160,7 +12160,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">350</context>
</context-group>
<target state="translated">Koncový bod LLM</target>
<target state="needs-translation">LLM Endpoint</target>
</trans-unit>
<trans-unit id="6572826277249350975" datatype="html">
<source>LLM Output Language</source>
@@ -12168,7 +12168,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">357</context>
</context-group>
<target state="translated">Výstupní jazyk LLM</target>
<target state="needs-translation">LLM Output Language</target>
</trans-unit>
<trans-unit id="3284403507172415792" datatype="html">
<source>Language to use for generated AI suggestions. When unset, AI suggestions use the user&apos;s display language if explicitly set.</source>
@@ -12176,7 +12176,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">361</context>
</context-group>
<target state="translated">Jazyk, který se má použít pro návrhy generované AI. Pokud není nastaveno, návrhy AI použijí jazyk zobrazení uživatele, je-li explicitně nastaven.</target>
<target state="needs-translation">Language to use for generated AI suggestions. When unset, AI suggestions use the user's display language if explicitly set.</target>
</trans-unit>
<trans-unit id="4493921125434706859" datatype="html">
<source>LLM Request Timeout</source>
@@ -12184,7 +12184,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">365</context>
</context-group>
<target state="translated">Čawsový limit žádosti LLM</target>
<target state="needs-translation">LLM Request Timeout</target>
</trans-unit>
<trans-unit id="483994032066441287" datatype="html">
<source>Timeout in seconds for LLM requests.</source>
@@ -12192,7 +12192,7 @@
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">369</context>
</context-group>
<target state="translated">Časový limit v sekundách pro žádosti LLM.</target>
<target state="needs-translation">Timeout in seconds for LLM requests.</target>
</trans-unit>
<trans-unit id="9155387182259025015" datatype="html">
<source>Processing</source>
+1 -1
View File
@@ -1528,7 +1528,7 @@
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
<context context-type="linenumber">306,308</context>
</context-group>
<target state="translated"> Die Einstellungen gelten für dieses Benutzerkonto in Bezug auf Objekte (Tags, E-Mail-Regeln usw.), die über die Weboberfläche erstellt wurden. Diese Einstellungen gelten nicht für Dokumente. </target>
<target state="needs-translation"> Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI. These settings do not apply to documents. </target>
</trans-unit>
<trans-unit id="4292903881380648974" datatype="html" approved="yes">
<source>Default Owner</source>
+10 -10
View File
@@ -1528,7 +1528,7 @@
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
<context context-type="linenumber">306,308</context>
</context-group>
<target state="translated"> Innstillinger gjelder denne brukerkontoen for objekter (Etiketter, postregler, etc.) som er opprettet via web-grensesnittet. Disse innstillingene gjelder ikke for dokumenter. </target>
<target state="needs-translation"> Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI. These settings do not apply to documents. </target>
</trans-unit>
<trans-unit id="4292903881380648974" datatype="html">
<source>Default Owner</source>
@@ -2076,7 +2076,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">96</context>
</context-group>
<target state="translated">Søk oppgaver</target>
<target state="needs-translation">Search tasks</target>
</trans-unit>
<trans-unit id="6849725902312323996" datatype="html">
<source>Reset filters</source>
@@ -2620,7 +2620,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">123</context>
</context-group>
<target state="translated">E-post-import</target>
<target state="translated">E-postforbruk</target>
</trans-unit>
<trans-unit id="29832309535656200" datatype="html">
<source>System</source>
@@ -4924,7 +4924,7 @@
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">32</context>
</context-group>
<target state="translated">Paperless vil bare behandle e-poster som passer til <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;em&gt;"/>hele<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;/em&gt;"/> av kriteriene som er angitt nedenfor.</target>
<target state="translated">Paperløst vil bare behandle e-poster som passer til <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;em&gt;"/>hele<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;/em&gt;"/> av kriteriene som er angitt nedenfor.</target>
</trans-unit>
<trans-unit id="7046259383943324039" datatype="html">
<source>Folder</source>
@@ -6810,7 +6810,7 @@
<context context-type="sourcefile">src/app/components/common/input/tags/tags.component.html</context>
<context context-type="linenumber">56</context>
</context-group>
<target state="translated">Filtrer dokumenter med disse etikettene</target>
<target state="translated">Filtrer dokumenter med disse Etikettene</target>
</trans-unit>
<trans-unit id="1400555558847223243" datatype="html">
<source>Suggestion:</source>
@@ -7478,7 +7478,7 @@
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">68</context>
</context-group>
<target state="translated">Del kobling</target>
<target state="needs-translation">Share link</target>
</trans-unit>
<trans-unit id="2618101617221155143" datatype="html">
<source>Copy link</source>
@@ -8416,7 +8416,7 @@
<context context-type="sourcefile">src/app/components/document-asn/document-asn.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
<target state="translated">Søker etter dokument med asn<x id="INTERPOLATION" equiv-text="{{asn()}}"/></target>
<target state="needs-translation">Searching document with asn <x id="INTERPOLATION" equiv-text="{{asn()}}"/></target>
</trans-unit>
<trans-unit id="1407560924967345762" datatype="html">
<source>Page</source>
@@ -9720,7 +9720,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">8</context>
</context-group>
<target state="translated"><x id="INTERPOLATION" equiv-text="document().title | documentTitle }}"/> miniatyr</target>
<target state="needs-translation"><x id="INTERPOLATION" equiv-text="document().title | documentTitle }}"/> thumbnail</target>
</trans-unit>
<trans-unit id="2784168796433474565" datatype="html">
<source>Filter by tag</source>
@@ -10008,7 +10008,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">135</context>
</context-group>
<target state="translated">{VAR_PLURAL, plural, one {}=1 {1 dokument} other {<x id="INTERPOLATION"/> dokumenter}}</target>
<target state="translated">{VAR_PLURAL, plural, one {}=1 {1 dokument} other {E-post <x id="INTERPOLATION"/> dokumenter}}</target>
</trans-unit>
<trans-unit id="2243770355958919528" datatype="html">
<source>(filtered)</source>
@@ -11328,7 +11328,7 @@
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
<context context-type="linenumber">29</context>
</context-group>
<target state="translated">Velg alle behandlede e-postmeldinger</target>
<target state="needs-translation">Select all processed email messages</target>
</trans-unit>
<trans-unit id="8691920320483720007" datatype="html">
<source>Received</source>
+23 -23
View File
@@ -1528,7 +1528,7 @@
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
<context context-type="linenumber">306,308</context>
</context-group>
<target state="translated"> Nastavitve veljajo za ta uporabniški račun za objekte (oznake, pravila za pošto itd.), ustvarjene prek spletnega uporabniškega vmesnika. Te nastavitve ne veljajo za dokumente. </target>
<target state="needs-translation"> Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI. These settings do not apply to documents. </target>
</trans-unit>
<trans-unit id="4292903881380648974" datatype="html">
<source>Default Owner</source>
@@ -2076,7 +2076,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">96</context>
</context-group>
<target state="translated">Iskanje nalog</target>
<target state="needs-translation">Search tasks</target>
</trans-unit>
<trans-unit id="6849725902312323996" datatype="html">
<source>Reset filters</source>
@@ -2704,7 +2704,7 @@
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
<context context-type="linenumber">349</context>
</context-group>
<target state="translated">Opustim vse naloge <x id="PH" equiv-text="this.totalTasks()"/>?</target>
<target state="needs-translation">Dismiss all <x id="PH" equiv-text="this.totalTasks()"/> tasks?</target>
</trans-unit>
<trans-unit id="8149502458056418229" datatype="html">
<source>Success. New document id <x id="PH" equiv-text="documentId"/> created</source>
@@ -7378,7 +7378,7 @@
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">22</context>
</context-group>
<target state="translated">+ <x id="INTERPOLATION" equiv-text="{{ selectionCount() - documentPreview().length }}"/> več…</target>
<target state="needs-translation">+ <x id="INTERPOLATION" equiv-text="{{ selectionCount() - documentPreview().length }}"/> more…</target>
</trans-unit>
<trans-unit id="8037476586059399916" datatype="html">
<source>Expires</source>
@@ -7478,7 +7478,7 @@
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
<context context-type="linenumber">68</context>
</context-group>
<target state="translated">Deli povezavo</target>
<target state="needs-translation">Share link</target>
</trans-unit>
<trans-unit id="2618101617221155143" datatype="html">
<source>Copy link</source>
@@ -7942,7 +7942,7 @@
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">147</context>
</context-group>
<target state="translated">Nedavna aktivnost opravila <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;small text-muted fw-light&quot;&gt;"/>(<x id="INTERPOLATION" equiv-text="{{status().tasks.summary.days}}"/> dni)<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
<target state="needs-translation">Recent Task Activity <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;small text-muted fw-light&quot;&gt;"/>(<x id="INTERPOLATION" equiv-text="{{status().tasks.summary.days}}"/> days)<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
</trans-unit>
<trans-unit id="3448462145758383019" datatype="html">
<source>Total</source>
@@ -8416,7 +8416,7 @@
<context context-type="sourcefile">src/app/components/document-asn/document-asn.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
<target state="translated">Iskanje dokumenta z asn <x id="INTERPOLATION" equiv-text="{{asn()}}"/></target>
<target state="needs-translation">Searching document with asn <x id="INTERPOLATION" equiv-text="{{asn()}}"/></target>
</trans-unit>
<trans-unit id="1407560924967345762" datatype="html">
<source>Page</source>
@@ -8440,7 +8440,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">7,8</context>
</context-group>
<target state="translated">od <x id="INTERPOLATION" equiv-text="{{previewNumPages()}}"/></target>
<target state="needs-translation">of <x id="INTERPOLATION" equiv-text="{{previewNumPages()}}"/></target>
</trans-unit>
<trans-unit id="8590109102084543521" datatype="html">
<source>-</source>
@@ -8660,7 +8660,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">318</context>
</context-group>
<target state="translated">Izvirna kontrolna vsota SHA256</target>
<target state="needs-translation">Original SHA256 checksum</target>
</trans-unit>
<trans-unit id="5888243105821763422" datatype="html">
<source>Original file size</source>
@@ -8684,7 +8684,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">331</context>
</context-group>
<target state="translated">Arhivska kontrolna vsota SHA256</target>
<target state="needs-translation">Archive SHA256 checksum</target>
</trans-unit>
<trans-unit id="6033581412811562084" datatype="html">
<source>Archive file size</source>
@@ -8716,7 +8716,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">368,371</context>
</context-group>
<target state="translated">Opombe <x id="START_BLOCK_IF" equiv-text="@if (document()?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></target>
<target state="needs-translation">Notes <x id="START_BLOCK_IF" equiv-text="@if (document()?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></target>
</trans-unit>
<trans-unit id="186236568870281953" datatype="html">
<source>History</source>
@@ -8732,7 +8732,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">401,405</context>
</context-group>
<target state="translated"> Dvojniki <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="cate_documents.length }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/></target>
<target state="needs-translation"> Duplicates <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="cate_documents.length }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/></target>
</trans-unit>
<trans-unit id="6449374629822973702" datatype="html">
<source>Duplicate documents detected:</source>
@@ -8936,7 +8936,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1271</context>
</context-group>
<target state="translated">Napaka pri shranjevanju dokumenta "<x id="PH" equiv-text="this.document().title"/>"</target>
<target state="needs-translation">Error saving document "<x id="PH" equiv-text="this.document().title"/>"</target>
</trans-unit>
<trans-unit id="448882439049417053" datatype="html">
<source>Error saving document</source>
@@ -8952,7 +8952,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1359</context>
</context-group>
<target state="translated">Ali res želite premakniti dokument "<x id="PH" equiv-text="this.document().title"/>" v koš?</target>
<target state="needs-translation">Do you really want to move the document "<x id="PH" equiv-text="this.document().title"/>" to the trash?</target>
</trans-unit>
<trans-unit id="282586936710748252" datatype="html">
<source>Documents can be restored prior to permanent deletion.</source>
@@ -9008,7 +9008,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1413</context>
</context-group>
<target state="translated">V ozadju se bo začel postopek ponovne obdelave za "<x id="PH" equiv-text="this.document().title"/>".</target>
<target state="needs-translation">Reprocess operation for "<x id="PH" equiv-text="this.document().title"/>" will begin in the background.</target>
</trans-unit>
<trans-unit id="4409560272830824468" datatype="html">
<source>Error executing operation</source>
@@ -9040,7 +9040,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1808</context>
</context-group>
<target state="translated">Urejanje PDF-ja za "<x id="PH" equiv-text="this.document().title"/>" se bo začelo v ozadju.</target>
<target state="needs-translation">PDF edit operation for "<x id="PH" equiv-text="this.document().title"/>" will begin in the background.</target>
</trans-unit>
<trans-unit id="9043972994040261999" datatype="html">
<source>Error executing PDF edit operation</source>
@@ -9064,7 +9064,7 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1865</context>
</context-group>
<target state="translated">Operacija odstranitve gesla za "<x id="PH" equiv-text="this.document().title"/>" se bo začela v ozadju.</target>
<target state="needs-translation">Password removal operation for "<x id="PH" equiv-text="this.document().title"/>" will begin in the background.</target>
</trans-unit>
<trans-unit id="2282118435712883014" datatype="html">
<source>Error executing password removal operation</source>
@@ -9720,7 +9720,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">8</context>
</context-group>
<target state="translated"><x id="INTERPOLATION" equiv-text="document().title | documentTitle }}"/> sličica</target>
<target state="needs-translation"><x id="INTERPOLATION" equiv-text="document().title | documentTitle }}"/> thumbnail</target>
</trans-unit>
<trans-unit id="2784168796433474565" datatype="html">
<source>Filter by tag</source>
@@ -9756,7 +9756,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">95,96</context>
</context-group>
<target state="translated">Ustvarjeno: <x id="INTERPOLATION" equiv-text="{{ document().created | customDate }}"/></target>
<target state="needs-translation">Created: <x id="INTERPOLATION" equiv-text="{{ document().created | customDate }}"/></target>
</trans-unit>
<trans-unit id="2030261243264601523" datatype="html">
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document().added | customDate }}"/></source>
@@ -9772,7 +9772,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">96,97</context>
</context-group>
<target state="translated">Dodano: <x id="INTERPOLATION" equiv-text="{{ document().added | customDate }}"/></target>
<target state="needs-translation">Added: <x id="INTERPOLATION" equiv-text="{{ document().added | customDate }}"/></target>
</trans-unit>
<trans-unit id="4235671847487610290" datatype="html">
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document().modified | customDate }}"/></source>
@@ -9788,7 +9788,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">97,98</context>
</context-group>
<target state="translated">Spremenjeno: <x id="INTERPOLATION" equiv-text="{{ document().modified | customDate }}"/></target>
<target state="needs-translation">Modified: <x id="INTERPOLATION" equiv-text="{{ document().modified | customDate }}"/></target>
</trans-unit>
<trans-unit id="197162226430950645" datatype="html">
<source>{VAR_PLURAL, plural, =1 {1 page} other {<x id="INTERPOLATION"/> pages}}</source>
@@ -11312,7 +11312,7 @@
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
<context context-type="linenumber">2</context>
</context-group>
<target state="translated">Obdelana pošta za <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;em&gt;"/><x id="INTERPOLATION" equiv-text="{{ rule().name }}"/><x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;/em&gt;"/></target>
<target state="needs-translation">Processed Mail for <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;em&gt;"/><x id="INTERPOLATION" equiv-text="{{ rule().name }}"/><x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;/em&gt;"/></target>
</trans-unit>
<trans-unit id="1991019495862291373" datatype="html">
<source>No processed email messages found.</source>
@@ -11328,7 +11328,7 @@
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
<context context-type="linenumber">29</context>
</context-group>
<target state="translated">Izberi vsa obdelana e-poštna sporočila</target>
<target state="needs-translation">Select all processed email messages</target>
</trans-unit>
<trans-unit id="8691920320483720007" datatype="html">
<source>Received</source>
+4 -3
View File
@@ -839,8 +839,9 @@ class ConsumerPlugin(
self.log.debug(f"Creation date from parse_date: {create_date}")
else:
stats = Path(self.input_doc.original_file).stat()
create_date = timezone.make_aware(
datetime.datetime.fromtimestamp(stats.st_mtime),
create_date = datetime.datetime.fromtimestamp(
stats.st_mtime,
tz=datetime.UTC,
)
self.log.debug(f"Creation date from st_mtime: {create_date}")
@@ -954,7 +955,7 @@ class ConsumerPlugin(
try:
copy_basic_file_stats(source, target)
except Exception: # pragma: no cover
pass
self.log.debug("Unable to copy file stats from %s to %s", source, target)
class ConsumerPreflightPlugin(
+4 -4
View File
@@ -1,4 +1,3 @@
import datetime as dt
import logging
import os
import shutil
@@ -6,6 +5,7 @@ from pathlib import Path
from typing import Final
from django.conf import settings
from django.utils import timezone
from pikepdf import Pdf
from documents.consumer import ConsumerError
@@ -78,7 +78,7 @@ class CollatePlugin(NoCleanupPluginMixin, NoSetupPluginMixin, ConsumeTaskPlugin)
stats = staging.stat()
# if the file is older than the timeout, we don't consider
# it valid
if (dt.datetime.now().timestamp() - stats.st_mtime) > TIMEOUT_SECONDS:
if (timezone.now().timestamp() - stats.st_mtime) > TIMEOUT_SECONDS:
logger.warning("Outdated double sided staging file exists, deleting it")
staging.unlink()
else:
@@ -99,7 +99,7 @@ class CollatePlugin(NoCleanupPluginMixin, NoSetupPluginMixin, ConsumeTaskPlugin)
"two uploaded files don't belong to the same double-"
"sided scan. Please retry, starting with the odd "
"numbered pages again.",
)
) from None
# Merged file has the same path, but without the
# double-sided subdir. Therefore, it is also in the
# consumption dir and will be picked up for processing
@@ -134,7 +134,7 @@ class CollatePlugin(NoCleanupPluginMixin, NoSetupPluginMixin, ConsumeTaskPlugin)
shutil.move(pdf_file, staging)
# update access to modification time so we know if the file
# is outdated when another file gets uploaded
timestamp = dt.datetime.now().timestamp()
timestamp = timezone.now().timestamp()
os.utime(staging, (timestamp, timestamp))
logger.info(
"Got scan with odd numbered pages of double-sided scan, moved it to %s",
+5 -32
View File
@@ -37,7 +37,6 @@ from drf_spectacular.utils import extend_schema_field
from guardian.utils import get_group_obj_perms_model
from guardian.utils import get_user_obj_perms_model
from rest_framework import serializers
from rest_framework.filters import BaseFilterBackend
from rest_framework.filters import OrderingFilter
from rest_framework_guardian.filters import ObjectPermissionsFilter
@@ -51,7 +50,6 @@ from documents.models import ShareLink
from documents.models import ShareLinkBundle
from documents.models import StoragePath
from documents.models import Tag
from documents.permissions import permitted_document_ids
if TYPE_CHECKING:
from collections.abc import Callable
@@ -359,7 +357,7 @@ def handle_validation_prefix(func: Callable):
try:
return func(*args, **kwargs)
except serializers.ValidationError as e:
raise serializers.ValidationError({validation_prefix: e.detail})
raise serializers.ValidationError({validation_prefix: e.detail}) from e
# Update the signature to include the validation_prefix argument
old_sig = inspect.signature(func)
@@ -470,7 +468,7 @@ class CustomFieldQueryParser:
except json.JSONDecodeError:
raise serializers.ValidationError(
{self._validation_prefix: [_("Value must be valid JSON.")]},
)
) from None
return (
self._parse_expr(expr, validation_prefix=self._validation_prefix),
self._annotations,
@@ -598,7 +596,7 @@ class CustomFieldQueryParser:
except CustomField.DoesNotExist:
raise serializers.ValidationError(
[_("{name!r} is not a valid custom field.").format(name=id_or_name)],
)
) from None
self._custom_fields[custom_field.id] = custom_field
self._custom_fields[custom_field.name] = custom_field
return custom_field
@@ -731,7 +729,7 @@ class CustomFieldQueryParser:
)
# Check if any of the requested IDs are missing.
missing_ids = set(value) - set(link.document_id for link in links)
missing_ids = set(value) - {link.document_id for link in links}
if missing_ids:
# The result should be an empty set in this case.
return Q(id__in=[])
@@ -1040,31 +1038,6 @@ class ObjectOwnedOrGrantedPermissionsFilter(ObjectPermissionsFilter):
return objects_with_perms | objects_owned | objects_unowned
class DocumentPermissionsFilter(BaseFilterBackend):
"""
A filter backend limiting Document results to those the requesting user
owns, are unowned, or has explicit (user- or group-level) view
permission on.
Unlike ``ObjectOwnedOrGrantedPermissionsFilter``, this does not build an
``objects_with_perms | objects_owned | objects_unowned`` union of
querysets derived from the same base queryset. When that base queryset
already carries independent joins on a multi-valued relation (e.g. two
separate joins from ``tags__id__all`` filtering on two tags), each
OR-ed branch can end up pairing those joins' aliases differently,
letting more than one row out of the join's cross product satisfy the
combined WHERE -- returning the same document more than once. Filtering
via a single ``id__in`` against ``permitted_document_ids`` (a plain
subquery, not a join) sidesteps that entirely and is also cheaper than
guardian's join-based permission check.
"""
def filter_queryset(self, request, queryset, view):
if request.user.is_superuser:
return queryset
return queryset.filter(id__in=permitted_document_ids(request.user))
class ObjectOwnedPermissionsFilter(ObjectPermissionsFilter):
"""
A filter backend that limits results to those where the requesting user
@@ -1092,7 +1065,7 @@ class DocumentsOrderingFilter(OrderingFilter):
except CustomField.DoesNotExist:
raise serializers.ValidationError(
{self.prefix + str(custom_field_id): [_("Custom field not found")]},
)
) from None
annotation = None
match field.data_type:
@@ -7,6 +7,7 @@ from itertools import islice
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import Self
from allauth.mfa.models import Authenticator
from allauth.socialaccount.models import SocialAccount
@@ -154,7 +155,7 @@ class StreamingManifestWriter:
return
self._tmp_path.rename(self._path)
def __enter__(self) -> "StreamingManifestWriter":
def __enter__(self) -> Self:
self.open()
return self
@@ -480,7 +481,7 @@ class Command(CryptMixin, PaperlessCommand):
}
# 3. Export files from each document
for index, document_dict in enumerate(
for _, document_dict in enumerate(
self.track(
document_manifest,
description="Exporting documents...",
@@ -133,11 +133,14 @@ def _build_suggestion_table(
else:
doc_cell = Text(f"{doc} [{doc.pk}]")
tag_parts: list[str] = []
for tag in sorted(suggestion.tags_to_add, key=lambda t: t.name):
tag_parts.append(f"[green]+{tag.name}[/green]")
for tag in sorted(suggestion.tags_to_remove, key=lambda t: t.name):
tag_parts.append(f"[red]-{tag.name}[/red]")
tag_parts: list[str] = [
f"[green]+{tag.name}[/green]"
for tag in sorted(suggestion.tags_to_add, key=lambda t: t.name)
]
tag_parts.extend(
f"[red]-{tag.name}[/red]"
for tag in sorted(suggestion.tags_to_remove, key=lambda t: t.name)
)
tag_cell = Text.from_markup(", ".join(tag_parts)) if tag_parts else Text("-")
table.add_row(
+4 -5
View File
@@ -300,7 +300,7 @@ def consumable_document_matches_workflow(
]:
reason = (
f"Document source {document.source.name} not in"
f" {[DocumentSource(int(x)).name for x in trigger.sources]}",
f" {[DocumentSource(int(x)).name for x in trigger.sources]}"
)
trigger_matched = False
@@ -310,8 +310,7 @@ def consumable_document_matches_workflow(
and document.mailrule_id != trigger.filter_mailrule.pk
):
reason = (
f"Document mail rule {document.mailrule_id}"
f" != {trigger.filter_mailrule.pk}",
f"Document mail rule {document.mailrule_id} != {trigger.filter_mailrule.pk}"
)
trigger_matched = False
@@ -326,7 +325,7 @@ def consumable_document_matches_workflow(
):
reason = (
f"Document filename {document.original_file.name} does not match"
f" {trigger.filter_filename.lower()}",
f" {trigger.filter_filename.lower()}"
)
trigger_matched = False
@@ -349,7 +348,7 @@ def consumable_document_matches_workflow(
):
reason = (
f"Document path {document.original_file}"
f" does not match {trigger.filter_path}",
f" does not match {trigger.filter_path}"
)
trigger_matched = False
+3 -3
View File
@@ -373,7 +373,7 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
If the queryset already annotated ``effective_content``, that value is used.
"""
if hasattr(self, "effective_content"):
return getattr(self, "effective_content")
return self.effective_content
if self.root_document_id is not None or self.pk is None:
return self.content
@@ -1214,8 +1214,8 @@ class CustomFieldInstance(SoftDeleteModel):
def get_value_field_name(cls, data_type: CustomField.FieldDataType):
try:
return cls.TYPE_TO_DATA_STORE_NAME_MAP[data_type]
except KeyError: # pragma: no cover
raise NotImplementedError(data_type)
except KeyError as exc: # pragma: no cover
raise NotImplementedError(data_type) from exc
@property
def value(self):
+1 -1
View File
@@ -110,7 +110,7 @@ def run_convert(
args += ["-define", "pdf:use-cropbox=true"] if use_cropbox else []
args += [str(input_file), str(output_file)]
logger.debug("Execute: " + " ".join(args), extra={"group": logging_group})
logger.debug("Execute: %s", " ".join(args), extra={"group": logging_group})
try:
run_subprocess(args, environment, logger)
+3 -3
View File
@@ -163,7 +163,7 @@ def set_permissions_for_object(
)
def permitted_document_ids(user):
def _permitted_document_ids(user):
"""
Return a queryset of document IDs the user may view, limited to non-deleted
documents. This intentionally avoids ``get_objects_for_user`` to keep the
@@ -220,7 +220,7 @@ def get_document_count_filter_for_user(user, related_name: str = "documents"):
# Superuser: no permission filtering needed
return Q(**{f"{related_name}__deleted_at__isnull": True})
permitted_ids = permitted_document_ids(user)
permitted_ids = _permitted_document_ids(user)
return Q(**{f"{related_name}__id__in": permitted_ids})
@@ -311,7 +311,7 @@ def annotate_document_count_for_related_queryset(
queryset,
through_model=through_model,
related_object_field=related_object_field,
document_ids=permitted_document_ids(user),
document_ids=_permitted_document_ids(user),
target_field=target_field,
)
+1 -2
View File
@@ -67,8 +67,7 @@ class DateParserPluginBase(ABC):
Subclasses can override this to release resources.
"""
# Default implementation does nothing.
# Returning None implies exceptions are propagated.
return
def _parse_string(
self,
+7 -3
View File
@@ -204,12 +204,12 @@ class WriteBatch:
try:
self._lock.acquire(timeout=self._lock_timeout)
break
except filelock.Timeout:
except filelock.Timeout as exc:
if attempt == _LOCK_RETRY_ATTEMPTS - 1:
raise SearchIndexLockError(
f"Could not acquire index lock after {_LOCK_RETRY_ATTEMPTS} "
f"attempts (timeout={self._lock_timeout}s each)",
)
) from exc
sleep_s = random.uniform(
0,
min(_LOCK_BACKOFF_CAP, _LOCK_BACKOFF_BASE * (2**attempt)),
@@ -702,7 +702,11 @@ class TantivyBackend:
result_ids = cast("list[int]", searcher.fast_field_values("id", result_addrs))
addr_by_id: dict[int, tuple[float, tantivy.DocAddress]] = {
doc_id: (score, addr)
for (score, addr), doc_id in zip(batch_results.hits, result_ids)
for (score, addr), doc_id in zip(
batch_results.hits,
result_ids,
strict=False,
)
}
snippet_generator = None
+27 -19
View File
@@ -203,7 +203,7 @@ class MatchingModelSerializer(serializers.ModelSerializer[Any]):
logger.debug(f"Invalid regular expression: {e!s}")
raise serializers.ValidationError(
"Invalid regular expression, see log for details.",
)
) from None
return match
@@ -432,7 +432,7 @@ class OwnedObjectSerializer(
return set()
ctype = ContentType.objects.get_for_model(first_obj)
object_pks = list(obj.pk for obj in objects)
object_pks = [obj.pk for obj in objects]
pk_type = type(first_obj.pk)
def get_pks_for_permission_type(model):
@@ -926,7 +926,9 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer[CustomFieldInsta
try:
value_int = int(data["value"])
except (TypeError, ValueError):
raise serializers.ValidationError("Enter a valid integer.")
raise serializers.ValidationError(
"Enter a valid integer.",
) from None
# Keep values within the PostgreSQL integer range
MinValueValidator(-2147483648)(value_int)
MaxValueValidator(2147483647)(value_int)
@@ -958,7 +960,7 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer[CustomFieldInsta
except Exception:
raise serializers.ValidationError(
f"Value must be an id of an element in {select_options}",
)
) from None
elif field.data_type == CustomField.FieldDataType.DOCUMENTLINK:
if not (isinstance(data["value"], list) or data["value"] is None):
raise serializers.ValidationError(
@@ -1149,7 +1151,7 @@ class DocumentSerializer(
def to_representation(self, instance):
doc = super().to_representation(instance)
if "content" in self.fields and hasattr(instance, "effective_content"):
doc["content"] = getattr(instance, "effective_content") or ""
doc["content"] = instance.effective_content or ""
if self.truncate_content and "content" in self.fields:
doc["content"] = doc.get("content")[0:550]
return doc
@@ -1512,7 +1514,7 @@ class SavedViewSerializer(OwnedObjectSerializer):
)
)
except serializers.ValidationError as exc:
raise serializers.ValidationError({field_name: exc.detail})
raise serializers.ValidationError({field_name: exc.detail}) from exc
del normalized_data[field_name]
ret = super().to_internal_value(normalized_data)
@@ -1816,7 +1818,7 @@ class BulkEditSerializer(
logger.exception(f"Error validating custom fields: {e}")
raise serializers.ValidationError(
f"{name} must be a list of integers or a dict of id:value pairs, see the log for details",
)
) from None
elif not isinstance(custom_fields, list) or not all(
isinstance(i, int) for i in ids
):
@@ -1884,7 +1886,7 @@ class BulkEditSerializer(
try:
Tag.objects.get(id=tag_id)
except Tag.DoesNotExist:
raise serializers.ValidationError("Tag does not exist")
raise serializers.ValidationError("Tag does not exist") from None
else:
raise serializers.ValidationError("tag not specified")
@@ -1897,7 +1899,9 @@ class BulkEditSerializer(
try:
DocumentType.objects.get(id=document_type_id)
except DocumentType.DoesNotExist:
raise serializers.ValidationError("Document type does not exist")
raise serializers.ValidationError(
"Document type does not exist",
) from None
else:
raise serializers.ValidationError("document_type not specified")
@@ -1909,7 +1913,9 @@ class BulkEditSerializer(
try:
Correspondent.objects.get(id=correspondent_id)
except Correspondent.DoesNotExist:
raise serializers.ValidationError("Correspondent does not exist")
raise serializers.ValidationError(
"Correspondent does not exist",
) from None
else:
raise serializers.ValidationError("correspondent not specified")
@@ -1923,7 +1929,7 @@ class BulkEditSerializer(
except StoragePath.DoesNotExist:
raise serializers.ValidationError(
"Storage path does not exist",
)
) from None
else:
raise serializers.ValidationError("storage path not specified")
@@ -1978,7 +1984,7 @@ class BulkEditSerializer(
):
raise serializers.ValidationError("invalid rotation degrees")
except ValueError:
raise serializers.ValidationError("invalid rotation degrees")
raise serializers.ValidationError("invalid rotation degrees") from None
def _validate_source_mode(self, parameters) -> None:
source_mode = parameters.get(
@@ -2008,7 +2014,7 @@ class BulkEditSerializer(
pages.append([int(doc)])
parameters["pages"] = pages
except ValueError:
raise serializers.ValidationError("invalid pages specified")
raise serializers.ValidationError("invalid pages specified") from None
if "delete_originals" in parameters:
if not isinstance(parameters["delete_originals"], bool):
@@ -2278,14 +2284,14 @@ class PostDocumentSerializer(serializers.Serializer[dict[str, Any]]):
raise serializers.ValidationError(
_("Custom field id must be an integer: %(id)s")
% {"id": field_id},
)
) from None
try:
field = CustomField.objects.get(id=field_id_int)
except CustomField.DoesNotExist:
raise serializers.ValidationError(
_("Custom field with id %(id)s does not exist")
% {"id": field_id_int},
)
) from None
custom_field_serializer.validate(
{
"field": field,
@@ -2302,7 +2308,7 @@ class PostDocumentSerializer(serializers.Serializer[dict[str, Any]]):
_(
"Custom fields must be a list of integers or an object mapping ids to values.",
),
)
) from None
if CustomField.objects.filter(id__in=ids).count() != len(set(ids)):
raise serializers.ValidationError(
_("Some custom fields don't exist or were specified twice."),
@@ -2413,7 +2419,9 @@ class EmailSerializer(DocumentListSerializer):
for address in address_list:
email_validator(address)
except ValidationError:
raise serializers.ValidationError(f"Invalid email address: {address}")
raise serializers.ValidationError(
f"Invalid email address: {address}",
) from None
return ",".join(address_list)
@@ -2859,7 +2867,7 @@ class ShareLinkBundleSerializer(OwnedObjectSerializer):
return share_link_bundle
def get_document_count(self, obj: ShareLinkBundle) -> int:
return getattr(obj, "document_total") or obj.documents.count()
return obj.document_total or obj.documents.count()
class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin):
@@ -3207,7 +3215,7 @@ class WorkflowActionSerializer(serializers.ModelSerializer[WorkflowAction]):
except (ValueError, KeyError) as e:
raise serializers.ValidationError(
{"assign_title": f'Invalid f-string detected: "{e.args[0]}"'},
)
) from None
if (
"type" in attrs
+1 -1
View File
@@ -636,7 +636,7 @@ def update_filename_and_move_files(
# so this is not the end of the world.
# B: if moving the original file failed, nothing has changed
# anyway.
pass
logger.debug("Unable to restore previous file locations", exc_info=True)
# restore old values on the instance
instance.filename = old_filename
-2
View File
@@ -311,7 +311,6 @@ def sanity_check(*, raise_on_error: bool = True) -> str:
def bulk_update_documents(document_ids) -> None:
from documents.search import get_backend
document_ids = list(document_ids)
documents = Document.objects.filter(id__in=document_ids)
for doc in documents:
@@ -332,7 +331,6 @@ def bulk_update_documents(document_ids) -> None:
if ai_config.llm_index_enabled:
update_llm_index(
rebuild=False,
document_ids=document_ids,
)
@@ -29,9 +29,7 @@ class SimpleCommand(PaperlessCommand):
def handle(self, *args, **options):
items = list(range(5))
results = []
for item in self.track(items, description="Processing..."):
results.append(item * 2)
results = [item * 2 for item in self.track(items, description="Processing...")]
self.stdout.write(f"Results: {results}")
@@ -57,13 +55,13 @@ class MultiprocessCommand(PaperlessCommand):
def handle(self, *args, **options):
items = list(range(5))
results = []
for result in self.process_parallel(
_double_value,
items,
description="Processing...",
):
results.append(result)
results = list(
self.process_parallel(
_double_value,
items,
description="Processing...",
),
)
successes = sum(1 for r in results if r.success)
self.stdout.write(f"Successes: {successes}")
+5 -3
View File
@@ -341,9 +341,11 @@ class TestTranslateQuery:
("tag:foo,type:bar", "tag:foo AND document_type:bar"),
(
"created:[2020 TO 2021],added:[2022 TO 2023]",
"created:[2020-01-01T00:00:00Z TO 2022-01-01T00:00:00Z]"
" AND "
"added:[2022-01-01T00:00:00Z TO 2024-01-01T00:00:00Z]",
(
"created:[2020-01-01T00:00:00Z TO 2022-01-01T00:00:00Z]"
" AND "
"added:[2022-01-01T00:00:00Z TO 2024-01-01T00:00:00Z]"
),
),
# correspondent is not multi-value: comma stays literal inside the value
("correspondent:foo,bar", "correspondent:foo,bar"),
@@ -6,7 +6,6 @@ import zipfile
from django.contrib.auth.models import User
from django.test import override_settings
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APITestCase
@@ -33,21 +32,21 @@ class TestBulkDownload(DirectoriesMixin, SampleDirMixin, APITestCase):
filename="docA.pdf",
mime_type="application/pdf",
checksum="B",
created=timezone.make_aware(datetime.datetime(2021, 1, 1)),
created=datetime.datetime(2021, 1, 1, tzinfo=datetime.UTC),
)
self.doc2b = Document.objects.create(
title="document A",
filename="docA2.pdf",
mime_type="application/pdf",
checksum="D",
created=timezone.make_aware(datetime.datetime(2021, 1, 1)),
created=datetime.datetime(2021, 1, 1, tzinfo=datetime.UTC),
)
self.doc3 = Document.objects.create(
title="document B",
filename="docB.jpg",
mime_type="image/jpeg",
checksum="C",
created=timezone.make_aware(datetime.datetime(2020, 3, 21)),
created=datetime.datetime(2020, 3, 21, tzinfo=datetime.UTC),
archive_filename="docB.pdf",
archive_checksum="D",
)
+2 -2
View File
@@ -957,8 +957,8 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
for correspondent in response.data[field]:
self.assertEqual(correspondent["document_count"], 0)
self.assertCountEqual(
map(lambda c: c["id"], response.data[field]),
map(lambda c: c["id"], Entity.objects.values("id")),
(c["id"] for c in response.data[field]),
(c["id"] for c in Entity.objects.values("id")),
)
def test_api_selection_data(self) -> None:
@@ -1,5 +1,5 @@
import datetime
import json
from datetime import date
from unittest import mock
from unittest.mock import ANY
@@ -456,7 +456,7 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
},
)
date_value = date.today()
date_value = datetime.datetime.now(tz=datetime.UTC).date()
resp = self.client.patch(
f"/api/documents/{doc.id}/",
@@ -618,7 +618,7 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
data_type=CustomField.FieldDataType.DATE,
)
date_value = date.today()
date_value = datetime.datetime.now(tz=datetime.UTC).date()
resp = self.client.patch(
f"/api/documents/{doc.id}/",
+1 -89
View File
@@ -14,7 +14,6 @@ from unittest import mock
import celery
from dateutil import parser
from django.conf import settings
from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.core import mail
@@ -48,8 +47,6 @@ from documents.models import Workflow
from documents.models import WorkflowAction
from documents.models import WorkflowTrigger
from documents.signals.handlers import run_workflows
from documents.tests.factories import DocumentFactory
from documents.tests.factories import TagFactory
from documents.tests.utils import ConsumeTaskMixin
from documents.tests.utils import DirectoriesMixin
from documents.tests.utils import read_streaming_response
@@ -268,7 +265,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
created=date(2023, 1, 1),
)
created_datetime = datetime.datetime(2023, 2, 1, 12, 0, 0)
created_datetime = datetime.datetime(2023, 2, 1, 12, 0, 0, tzinfo=datetime.UTC)
response = self.client.patch(
f"/api/documents/{doc.pk}/",
{"created": created_datetime},
@@ -1215,91 +1212,6 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
[u1_doc1.id],
)
def test_document_owned_and_group_shared_not_duplicated_when_filtering_by_tags(
self,
) -> None:
"""
GIVEN:
- A document owned by a user and also shared with a group the user belongs to
WHEN:
- The user filters documents by more than one tag (tags__id__all)
THEN:
- The document is returned exactly once, not once per permission path
(regression test for https://github.com/paperless-ngx/paperless-ngx/issues/13331)
"""
user = User.objects.create_user("user1")
user.user_permissions.add(*Permission.objects.filter(codename="view_document"))
group = Group.objects.create(name="group1")
user.groups.add(group)
tag1 = TagFactory()
tag2 = TagFactory()
doc = DocumentFactory(title="shared", owner=user)
doc.tags.add(tag1, tag2)
assign_perm("view_document", group, doc)
self.client.force_authenticate(user=user)
response = self.client.get(
f"/api/documents/?tags__id__all={tag1.id},{tag2.id}",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["count"], 1)
self.assertEqual(response.data["results"][0]["id"], doc.id)
def test_document_permission_filter_excludes_unrelated_documents(self) -> None:
"""
GIVEN:
- A document owned by one user, with no permission granted to another user
WHEN:
- The unrelated user requests the document list
THEN:
- The document does not appear in their results
"""
owner = User.objects.create_user("owner1")
stranger = User.objects.create_user("stranger1")
stranger.user_permissions.add(
*Permission.objects.filter(codename="view_document"),
)
DocumentFactory(title="private", owner=owner)
self.client.force_authenticate(user=stranger)
response = self.client.get("/api/documents/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["count"], 0)
def test_document_permission_filter_only_visible_to_group_members(self) -> None:
"""
GIVEN:
- A document shared with a group via object permissions
WHEN:
- A group member and a non-member both request the document list
THEN:
- Only the group member sees the document
"""
owner = User.objects.create_user("owner2")
member = User.objects.create_user("member1")
non_member = User.objects.create_user("nonmember1")
for u in (member, non_member):
u.user_permissions.add(*Permission.objects.filter(codename="view_document"))
group = Group.objects.create(name="group2")
member.groups.add(group)
doc = DocumentFactory(title="shared2", owner=owner)
assign_perm("view_document", group, doc)
self.client.force_authenticate(user=member)
response = self.client.get("/api/documents/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["count"], 1)
self.assertEqual(response.data["results"][0]["id"], doc.id)
self.client.force_authenticate(user=non_member)
response = self.client.get("/api/documents/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["count"], 0)
def test_pagination_results(self) -> None:
"""
GIVEN:
+2 -2
View File
@@ -18,8 +18,8 @@ class MockOpenIDProvider:
def get_brands(self):
default_servers = [
dict(id="yahoo", name="Yahoo", openid_url="http://me.yahoo.com"),
dict(id="hyves", name="Hyves", openid_url="http://hyves.nl"),
{"id": "yahoo", "name": "Yahoo", "openid_url": "http://me.yahoo.com"},
{"id": "hyves", "name": "Hyves", "openid_url": "http://hyves.nl"},
]
return default_servers
+30 -14
View File
@@ -700,7 +700,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
pk=3,
checksum="C",
# specific time zone aware date
added=timezone.make_aware(datetime.datetime(2023, 12, 1)),
added=datetime.datetime(2023, 12, 1, tzinfo=datetime.UTC),
)
# refresh doc instance to ensure we operate on date objects that Django uses
# Django converts dates to UTC
@@ -1022,25 +1022,25 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
title="invoice",
content="the thing i bought at a shop and paid with bank account",
created=datetime.date(2018, 1, 1),
added=timezone.make_aware(datetime.datetime(2018, 1, 1)),
added=datetime.datetime(2018, 1, 1, tzinfo=datetime.UTC),
)
d2 = DocumentFactory(
title="bank statement 1",
content="things i paid for in august",
created=datetime.date(2019, 3, 4),
added=timezone.make_aware(datetime.datetime(2019, 3, 4)),
added=datetime.datetime(2019, 3, 4, tzinfo=datetime.UTC),
)
d3 = DocumentFactory(
title="bank statement 3",
content="things i paid for in september",
created=datetime.date(2020, 7, 9),
added=timezone.make_aware(datetime.datetime(2020, 7, 9)),
added=datetime.datetime(2020, 7, 9, tzinfo=datetime.UTC),
)
d4 = DocumentFactory(
title="Quarterly Report",
content="quarterly revenue profit margin earnings growth",
created=datetime.date(2021, 11, 30),
added=timezone.make_aware(datetime.datetime(2021, 11, 30)),
added=datetime.datetime(2021, 11, 30, tzinfo=datetime.UTC),
)
backend = get_backend()
backend.add_or_update(d1)
@@ -1159,7 +1159,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
d4.tags.add(t2)
d5 = Document.objects.create(
checksum="5",
added=timezone.make_aware(datetime.datetime(2020, 7, 13)),
added=datetime.datetime(2020, 7, 13, tzinfo=datetime.UTC),
content="test",
original_filename="doc5.pdf",
)
@@ -1269,14 +1269,18 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
d4.id,
search_query(
"&created__date__lt="
+ datetime.datetime(2020, 9, 2).strftime("%Y-%m-%d"),
+ datetime.datetime(2020, 9, 2, tzinfo=datetime.UTC).strftime(
"%Y-%m-%d",
),
),
)
self.assertNotIn(
d4.id,
search_query(
"&created__date__gt="
+ datetime.datetime(2020, 9, 2).strftime("%Y-%m-%d"),
+ datetime.datetime(2020, 9, 2, tzinfo=datetime.UTC).strftime(
"%Y-%m-%d",
),
),
)
@@ -1284,14 +1288,18 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
d4.id,
search_query(
"&created__date__lt="
+ datetime.datetime(2020, 1, 2).strftime("%Y-%m-%d"),
+ datetime.datetime(2020, 1, 2, tzinfo=datetime.UTC).strftime(
"%Y-%m-%d",
),
),
)
self.assertIn(
d4.id,
search_query(
"&created__date__gt="
+ datetime.datetime(2020, 1, 2).strftime("%Y-%m-%d"),
+ datetime.datetime(2020, 1, 2, tzinfo=datetime.UTC).strftime(
"%Y-%m-%d",
),
),
)
@@ -1299,14 +1307,18 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
d5.id,
search_query(
"&added__date__lt="
+ datetime.datetime(2020, 9, 2).strftime("%Y-%m-%d"),
+ datetime.datetime(2020, 9, 2, tzinfo=datetime.UTC).strftime(
"%Y-%m-%d",
),
),
)
self.assertNotIn(
d5.id,
search_query(
"&added__date__gt="
+ datetime.datetime(2020, 9, 2).strftime("%Y-%m-%d"),
+ datetime.datetime(2020, 9, 2, tzinfo=datetime.UTC).strftime(
"%Y-%m-%d",
),
),
)
@@ -1314,7 +1326,9 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
d5.id,
search_query(
"&added__date__lt="
+ datetime.datetime(2020, 1, 2).strftime("%Y-%m-%d"),
+ datetime.datetime(2020, 1, 2, tzinfo=datetime.UTC).strftime(
"%Y-%m-%d",
),
),
)
@@ -1322,7 +1336,9 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
d5.id,
search_query(
"&added__date__gt="
+ datetime.datetime(2020, 1, 2).strftime("%Y-%m-%d"),
+ datetime.datetime(2020, 1, 2, tzinfo=datetime.UTC).strftime(
"%Y-%m-%d",
),
),
)
+4 -3
View File
@@ -777,7 +777,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
sig.set.return_value.apply_async.side_effect = Exception("boom")
mock_consume_file.return_value = sig
with self.assertRaises(Exception):
with self.assertRaisesRegex(Exception, "boom"):
bulk_edit.merge(doc_ids, delete_originals=True)
self.doc1.refresh_from_db()
@@ -1060,6 +1060,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
for call, expected_id in zip(
mock_consume_delay.call_args_list,
doc_ids,
strict=False,
):
task_kwargs = call.kwargs["kwargs"]
self.assertEqual(task_kwargs["input_doc"].root_document_id, expected_id)
@@ -1318,7 +1319,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
sig.apply_async.side_effect = Exception("boom")
mock_chord.return_value = sig
with self.assertRaises(Exception):
with self.assertRaisesRegex(Exception, "boom"):
bulk_edit.edit_pdf(doc_ids, operations, delete_original=True)
self.doc2.refresh_from_db()
@@ -1430,7 +1431,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
{"page": 9999}, # invalid page, forces error during PDF load
]
with self.assertLogs("paperless.bulk_edit", level="ERROR"):
with self.assertRaises(Exception):
with self.assertRaises(ValueError):
bulk_edit.edit_pdf(doc_ids, operations)
mock_group.assert_not_called()
mock_consume_file.assert_not_called()
+2 -2
View File
@@ -782,8 +782,8 @@ class TestClassifier(DirectoriesMixin, TestCase):
load_classifier(raise_exception=True)
Path(settings.MODEL_FILE).touch()
mock_load.side_effect = Exception()
with self.assertRaises(Exception):
mock_load.side_effect = RuntimeError()
with self.assertRaises(RuntimeError):
load_classifier(raise_exception=True)
+4 -4
View File
@@ -59,7 +59,7 @@ class TestDoubleSided(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
def create_staging_file(self, src="double-sided-odd.pdf", datetime=None) -> None:
shutil.copy(self.SAMPLE_DIR / src, self.staging_file)
if datetime is None:
datetime = dt.datetime.now()
datetime = dt.datetime.now(tz=dt.UTC)
os.utime(str(self.staging_file), (datetime.timestamp(),) * 2)
def test_odd_numbered_moved_to_staging(self) -> None:
@@ -79,8 +79,8 @@ class TestDoubleSided(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertIsFile(self.staging_file)
self.assertAlmostEqual(
dt.datetime.fromtimestamp(self.staging_file.stat().st_mtime),
dt.datetime.now(),
dt.datetime.fromtimestamp(self.staging_file.stat().st_mtime, tz=dt.UTC),
dt.datetime.now(tz=dt.UTC),
delta=dt.timedelta(seconds=5),
)
self.assertIn("Received odd numbered pages", msg["reason"])
@@ -124,7 +124,7 @@ class TestDoubleSided(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
self.create_staging_file(
datetime=dt.datetime.now()
datetime=dt.datetime.now(tz=dt.UTC)
- dt.timedelta(minutes=TIMEOUT_MINUTES, seconds=1),
)
msg = self.consume_file("double-sided-odd.pdf")
+20 -14
View File
@@ -12,7 +12,6 @@ from django.contrib.auth.models import User
from django.db import DatabaseError
from django.test import TestCase
from django.test import override_settings
from django.utils import timezone
from documents.file_handling import create_source_path_directory
from documents.file_handling import delete_empty_directories
@@ -452,7 +451,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
FILENAME_FORMAT="{created_year}-{created_month}-{created_day}",
)
def test_created_year_month_day(self) -> None:
d1 = timezone.make_aware(datetime.datetime(2020, 3, 6, 1, 1, 1))
d1 = datetime.datetime(2020, 3, 6, 1, 1, 1, tzinfo=datetime.UTC)
doc1 = Document.objects.create(
title="doc1",
mime_type="application/pdf",
@@ -469,7 +468,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
FILENAME_FORMAT="{added_year}-{added_month}-{added_day}",
)
def test_added_year_month_day(self) -> None:
d1 = timezone.make_aware(datetime.datetime(1232, 1, 9, 1, 1, 1))
d1 = datetime.datetime(1232, 1, 9, 1, 1, 1, tzinfo=datetime.UTC)
doc1 = Document.objects.create(
title="doc1",
mime_type="application/pdf",
@@ -482,7 +481,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertEqual(generate_filename(doc1), expected_filename)
doc1.added = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
doc1.added = datetime.datetime(2020, 11, 16, 1, 1, 1, tzinfo=datetime.UTC)
self.assertEqual(generate_filename(doc1), Path("2020-11-16.pdf"))
@@ -1266,7 +1265,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
def test_short_names_added(self) -> None:
doc = Document.objects.create(
title="The Title",
added=timezone.make_aware(datetime.datetime(1984, 8, 21, 7, 36, 51, 153)),
added=datetime.datetime(1984, 8, 21, 7, 36, 51, 153, tzinfo=datetime.UTC),
mime_type="application/pdf",
pk=2,
checksum="2",
@@ -1505,7 +1504,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
doc_a = Document.objects.create(
title="Does Matter",
created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
added=datetime.datetime(2024, 10, 1, 7, 36, 51, 153, tzinfo=datetime.UTC),
mime_type="application/pdf",
pk=2,
checksum="2",
@@ -1577,7 +1576,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
doc = Document.objects.create(
title="scan_017562",
created=datetime.date(2025, 7, 2),
added=timezone.make_aware(datetime.datetime(2026, 3, 3, 11, 53, 16)),
added=datetime.datetime(2026, 3, 3, 11, 53, 16, tzinfo=datetime.UTC),
mime_type="application/pdf",
checksum="test-checksum",
storage_path=sp,
@@ -1606,7 +1605,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
doc_a = Document.objects.create(
title="Does Matter",
created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
added=datetime.datetime(2024, 10, 1, 7, 36, 51, 153, tzinfo=datetime.UTC),
mime_type="application/pdf",
pk=2,
checksum="2",
@@ -1641,7 +1640,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
doc_a = Document.objects.create(
title="Does Matter",
created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
added=datetime.datetime(2024, 10, 1, 7, 36, 51, 153, tzinfo=datetime.UTC),
mime_type="application/pdf",
pk=2,
checksum="2",
@@ -1673,7 +1672,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
doc_a = Document.objects.create(
title="Some Title",
created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
added=datetime.datetime(2024, 10, 1, 7, 36, 51, 153, tzinfo=datetime.UTC),
mime_type="application/pdf",
pk=2,
checksum="2",
@@ -1778,7 +1777,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
doc_a = Document.objects.create(
title="Some Title",
created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
added=datetime.datetime(2024, 10, 1, 7, 36, 51, 153, tzinfo=datetime.UTC),
mime_type="application/pdf",
pk=2,
checksum="2",
@@ -1792,8 +1791,15 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
CustomFieldInstance.objects.create(
document=doc_a,
field=CustomField.objects.get(name="Invoice Date"),
value_date=timezone.make_aware(
datetime.datetime(2024, 10, 1, 7, 36, 51, 153),
value_date=datetime.datetime(
2024,
10,
1,
7,
36,
51,
153,
tzinfo=datetime.UTC,
),
)
@@ -1833,7 +1839,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
doc = Document.objects.create(
title="Some Title! With @ Special # Characters",
created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
added=datetime.datetime(2024, 10, 1, 7, 36, 51, 153, tzinfo=datetime.UTC),
mime_type="application/pdf",
pk=2,
checksum="2",
+1 -1
View File
@@ -160,7 +160,7 @@ class TestDateLocalization:
)
def test_localize_date_raises_type_error_for_invalid_input(
self,
invalid_value: None | list[object] | dict[Any, Any] | Literal[1698330605],
invalid_value: list[object] | dict[Any, Any] | Literal[1698330605] | None,
) -> None:
with pytest.raises(TypeError) as excinfo:
localize_date(invalid_value, "medium", "en_US")
+2 -6
View File
@@ -401,10 +401,6 @@ class TestAIIndex(DirectoriesMixin, TestCase):
"documents.tasks.update_llm_index",
) as update_llm_index,
):
doc_ids = [doc.pk for doc in docs]
tasks.bulk_update_documents(doc_ids)
tasks.bulk_update_documents([doc.pk for doc in docs])
self.assertEqual(update_document_in_llm_index.apply_async.call_count, 0)
update_llm_index.assert_called_once_with(
rebuild=False,
document_ids=doc_ids,
)
update_llm_index.assert_called_once()
+1 -32
View File
@@ -287,7 +287,7 @@ class TestViews(DirectoriesMixin, TestCase):
"change": {"users": [], "groups": []},
}
else:
assert False, f"Unexpected tag found: {tag['name']}"
raise AssertionError(f"Unexpected tag found: {tag['name']}")
def test_list_no_n_plus_1_queries(self) -> None:
"""
@@ -625,37 +625,6 @@ class TestAIChatStreamingView(DirectoriesMixin, TestCase):
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/event-stream")
mock_stream_chat.assert_called_once_with(
query_str="question",
documents=[self.document],
output_language=None,
)
@patch("documents.views.stream_chat_with_documents")
@patch("documents.views.get_objects_for_user_owner_aware")
@override_settings(AI_ENABLED=True)
def test_post_uses_user_display_language(
self,
mock_get_objects,
mock_stream_chat,
) -> None:
UiSettings.objects.create(user=self.user, settings={"language": "de-de"})
self.grant_view_document_permission()
mock_get_objects.return_value = [self.document]
mock_stream_chat.return_value = iter([b"data"])
response = self.client.post(
self.ENDPOINT,
data='{"q": "question"}',
content_type="application/json",
)
self.assertEqual(response.status_code, 200)
mock_stream_chat.assert_called_once_with(
query_str="question",
documents=[self.document],
output_language="de-de",
)
@patch("documents.views.stream_chat_with_documents")
@override_settings(AI_ENABLED=True)
+8 -1
View File
@@ -2799,7 +2799,14 @@ class TestWorkflows(
doc = Document.objects.create(
title="test",
)
self.assertRaises(Exception, document_matches_workflow, doc, w, 99)
self.assertRaisesRegex(
Exception,
"not yet supported",
document_matches_workflow,
doc,
w,
99,
)
def test_removal_action_document_updated_workflow(self) -> None:
"""
+3 -2
View File
@@ -129,11 +129,12 @@ def util_call_with_backoff(
status_codes.append(cause_exec.response.status_code)
warnings.warn(
f"HTTP Exception for {cause_exec.request.url} - {cause_exec}",
stacklevel=2,
)
else:
warnings.warn(f"Unexpected error: {e}")
warnings.warn(f"Unexpected error: {e}", stacklevel=2)
except Exception as e: # pragma: no cover
warnings.warn(f"Unexpected error: {e}")
warnings.warn(f"Unexpected error: {e}", stacklevel=2)
retry_count = retry_count + 1
+7 -5
View File
@@ -21,20 +21,22 @@ def uri_validator(value: str, allowed_schemes: set[str] | None = None) -> None:
parts = urlparse(value)
if not parts.scheme:
raise ValidationError(
_(f"Unable to parse URI {value}, missing scheme"),
_("Unable to parse URI %(value)s, missing scheme") % {"value": value},
params={"value": value},
)
elif not parts.netloc and not parts.path:
raise ValidationError(
_(f"Unable to parse URI {value}, missing net location or path"),
_("Unable to parse URI %(value)s, missing net location or path")
% {"value": value},
params={"value": value},
)
if allowed_schemes and parts.scheme not in allowed_schemes:
raise ValidationError(
_(
f"URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}",
),
"URI scheme '%(scheme)s' is not allowed. Allowed schemes: %(schemes)s",
)
% {"scheme": parts.scheme, "schemes": ", ".join(allowed_schemes)},
params={"value": value, "scheme": parts.scheme},
)
@@ -42,7 +44,7 @@ def uri_validator(value: str, allowed_schemes: set[str] | None = None) -> None:
raise
except Exception as e:
raise ValidationError(
_(f"Unable to parse URI {value}"),
_("Unable to parse URI %(value)s") % {"value": value},
params={"value": value},
) from e
+52 -67
View File
@@ -7,11 +7,11 @@ import tempfile
import zipfile
from collections import defaultdict
from collections import deque
from datetime import UTC
from datetime import datetime
from datetime import timedelta
from http import HTTPStatus
from pathlib import Path
from time import mktime
from time import sleep
from typing import TYPE_CHECKING
from typing import Any
@@ -61,7 +61,6 @@ from django.http import StreamingHttpResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.timezone import make_aware
from django.utils.translation import get_language
from django.utils.translation import gettext_lazy as _
from django.views import View
@@ -133,7 +132,6 @@ from documents.file_handling import format_filename
from documents.filters import CorrespondentFilterSet
from documents.filters import CustomFieldFilterSet
from documents.filters import DocumentFilterSet
from documents.filters import DocumentPermissionsFilter
from documents.filters import DocumentsOrderingFilter
from documents.filters import DocumentTypeFilterSet
from documents.filters import ObjectOwnedOrGrantedPermissionsFilter
@@ -289,7 +287,7 @@ def _get_more_like_id(query_params: dict[str, Any], user: User | None) -> int:
pk=more_like_doc_id,
)
except (TypeError, ValueError, Document.DoesNotExist):
raise PermissionDenied(_("Invalid more_like_id"))
raise PermissionDenied(_("Invalid more_like_id")) from None
if user and not has_perms_owner_aware(
user,
@@ -654,20 +652,6 @@ class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Tag]):
update_document_parent_tags(tag, new_parent)
def _get_llm_output_language(ai_config: AIConfig, request) -> str | None:
output_language = ai_config.llm_output_language
if (
not output_language
and hasattr(request.user, "ui_settings")
and isinstance(
request.user.ui_settings.settings,
dict,
)
):
output_language = request.user.ui_settings.settings.get("language")
return output_language
@extend_schema_view(**generate_object_with_permissions_schema(DocumentTypeSerializer))
class DocumentTypeViewSet(
PermissionsAwareDocumentCountMixin,
@@ -987,7 +971,7 @@ class DocumentViewSet(
DjangoFilterBackend,
SearchFilter,
DocumentsOrderingFilter,
DocumentPermissionsFilter,
ObjectOwnedOrGrantedPermissionsFilter,
)
filterset_class = DocumentFilterSet
search_fields = ("title", "correspondent__name", "effective_content")
@@ -1161,7 +1145,7 @@ class DocumentViewSet(
"root_document",
).get(pk=pk)
except Document.DoesNotExist:
raise Http404
raise Http404 from None
root_doc = get_root_document(doc)
if request.user is not None and not has_perms_owner_aware(
@@ -1324,7 +1308,7 @@ class DocumentViewSet(
"root_document",
).get(id=pk)
except Document.DoesNotExist:
raise Http404
raise Http404 from None
root_doc = get_root_document(
request_doc,
@@ -1440,7 +1424,7 @@ class DocumentViewSet(
try:
lang = detect(doc.content)
except Exception:
pass
logger.debug("Unable to detect language for document %s", doc.pk)
meta["lang"] = lang
return Response(meta)
@@ -1529,7 +1513,16 @@ class DocumentViewSet(
if not ai_config.ai_enabled:
return HttpResponseBadRequest("AI is required for this feature")
output_language = _get_llm_output_language(ai_config=ai_config, request=request)
output_language = ai_config.llm_output_language
if (
not output_language
and hasattr(request.user, "ui_settings")
and isinstance(
request.user.ui_settings.settings,
dict,
)
):
output_language = request.user.ui_settings.settings.get("language") or None
llm_cache_backend = (
f"{ai_config.llm_backend}:{output_language}"
if output_language
@@ -1557,7 +1550,6 @@ class DocumentViewSet(
"document %s: %s",
doc.pk,
exc,
exc_info=True,
)
raise ValidationError({"ai": [_("Invalid AI configuration.")]}) from exc
except LLMTimeoutError as exc:
@@ -1565,7 +1557,6 @@ class DocumentViewSet(
"AI backend timed out while generating suggestions for document %s: %s",
doc.pk,
exc,
exc_info=True,
)
return Response(
{"ai": [_("AI backend request timed out.")]},
@@ -1642,7 +1633,7 @@ class DocumentViewSet(
disposition="inline",
)
except FileNotFoundError:
raise Http404
raise Http404 from None
@action(methods=["get"], detail=True, filter_backends=[])
@method_decorator(cache_control(no_cache=True))
@@ -1667,14 +1658,14 @@ class DocumentViewSet(
return FileResponse(handle, content_type="image/webp")
except FileNotFoundError:
raise Http404
raise Http404 from None
@action(methods=["get"], detail=True)
def download(self, request, pk=None):
try:
return self.file_response(pk, request, "attachment")
except (FileNotFoundError, Document.DoesNotExist):
raise Http404
raise Http404 from None
@action(
methods=["get", "post", "delete"],
@@ -1699,7 +1690,7 @@ class DocumentViewSet(
):
return HttpResponseForbidden("Insufficient permissions to view notes")
except Document.DoesNotExist:
raise Http404
raise Http404 from None
serializer = self.get_serializer(doc)
@@ -1770,7 +1761,7 @@ class DocumentViewSet(
try:
note_id_int = int(note_id)
except ValueError:
raise ValidationError({"id": "A valid integer is required."})
raise ValidationError({"id": "A valid integer is required."}) from None
note = get_object_or_404(Note, id=note_id_int, document=doc)
if settings.AUDIT_LOG_ENABLED:
LogEntry.objects.log_create(
@@ -1814,7 +1805,7 @@ class DocumentViewSet(
"Insufficient permissions to add share link",
)
except Document.DoesNotExist:
raise Http404
raise Http404 from None
if request.method == "GET":
now = timezone.now()
@@ -1842,7 +1833,7 @@ class DocumentViewSet(
"Insufficient permissions",
)
except Document.DoesNotExist: # pragma: no cover
raise Http404
raise Http404 from None
# documents
entries = [
@@ -1863,28 +1854,28 @@ class DocumentViewSet(
]
# custom fields
for entry in LogEntry.objects.get_for_objects(
doc.custom_fields.all(),
).select_related("actor"):
entries.append(
{
"id": entry.id,
"timestamp": entry.timestamp,
"action": entry.get_action_display(),
"changes": {
"custom_fields": {
"type": "custom_field",
"field": str(entry.object_repr).split(":")[0].strip(),
"value": str(entry.object_repr).split(":")[1].strip(),
},
entries.extend(
{
"id": entry.id,
"timestamp": entry.timestamp,
"action": entry.get_action_display(),
"changes": {
"custom_fields": {
"type": "custom_field",
"field": str(entry.object_repr).split(":")[0].strip(),
"value": str(entry.object_repr).split(":")[1].strip(),
},
"actor": (
{"id": entry.actor.id, "username": entry.actor.username}
if entry.actor
else None
),
},
)
"actor": (
{"id": entry.actor.id, "username": entry.actor.username}
if entry.actor
else None
),
}
for entry in LogEntry.objects.get_for_objects(
doc.custom_fields.all(),
).select_related("actor")
)
return Response(sorted(entries, key=lambda x: x["timestamp"], reverse=True))
@@ -1995,13 +1986,13 @@ class DocumentViewSet(
):
return HttpResponseForbidden("Insufficient permissions")
except Document.DoesNotExist:
raise Http404
raise Http404 from None
try:
doc_name, doc_data = serializer.validated_data.get("document")
version_label = serializer.validated_data.get("version_label")
t = int(mktime(datetime.now().timetuple()))
t = int(timezone.now().timestamp())
settings.SCRATCH_DIR.mkdir(parents=True, exist_ok=True)
@@ -2046,7 +2037,7 @@ class DocumentViewSet(
"root_document",
).get(pk=pk)
except Document.DoesNotExist:
raise Http404
raise Http404 from None
return get_root_document(root_doc)
def _get_version_doc_for_root(self, root_doc: Document, version_id) -> Document:
@@ -2055,7 +2046,7 @@ class DocumentViewSet(
pk=version_id,
)
except Document.DoesNotExist:
raise Http404
raise Http404 from None
if (
version_doc.id != root_doc.id
@@ -2271,14 +2262,8 @@ class ChatStreamingView(GenericAPIView[Any]):
Document,
)
output_language = _get_llm_output_language(ai_config=ai_config, request=request)
response = StreamingHttpResponse(
stream_chat_with_documents(
query_str=question,
documents=documents,
output_language=output_language,
),
stream_chat_with_documents(query_str=question, documents=documents),
content_type="text/event-stream",
)
return response
@@ -2635,7 +2620,7 @@ class LogViewSet(ViewSet):
try:
limit = int(limit_param)
except (TypeError, ValueError):
raise ValidationError({"limit": "Must be a positive integer"})
raise ValidationError({"limit": "Must be a positive integer"}) from None
if limit < 1:
raise ValidationError({"limit": "Must be a positive integer"})
else:
@@ -3232,7 +3217,7 @@ class PostDocumentView(GenericAPIView[Any]):
cf = serializer.validated_data.get("custom_fields")
from_webui = serializer.validated_data.get("from_webui")
t = int(mktime(datetime.now().timetuple()))
t = int(timezone.now().timestamp())
settings.SCRATCH_DIR.mkdir(parents=True, exist_ok=True)
@@ -4044,7 +4029,7 @@ class UiSettingsView(GenericAPIView[Any]):
user_resp["last_name"] = user.last_name
# strip <app_label>.
roles = map(lambda perm: re.sub(r"^\w+.", "", perm), user.get_all_permissions())
roles = (re.sub(r"^\w+.", "", perm) for perm in user.get_all_permissions())
return Response(
{
"user": user_resp,
@@ -5134,7 +5119,7 @@ class SystemStatusView(PassUserMixin):
index_dir = settings.INDEX_DIR
mtimes = [p.stat().st_mtime for p in index_dir.iterdir() if p.is_file()]
index_last_modified = (
make_aware(datetime.fromtimestamp(max(mtimes))) if mtimes else None
datetime.fromtimestamp(max(mtimes), tz=UTC) if mtimes else None
)
except Exception as e:
index_status = "ERROR"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Afrikaans\n"
"Language: af_ZA\n"
@@ -1919,151 +1919,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Engels (VS)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arabies"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Belorussies"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Katalaans"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Tsjeggies"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Deens"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Duits"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Engels (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Spaans"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Fins"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Frans"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italiaans"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxemburgs"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Nederlands"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Pools"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugees (Brasilië)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugees"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Roemeens"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Russies"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Sloweens"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serwies"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Sweeds"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turks"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Vereenvoudigde Sjinees"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Amharic\n"
"Language: am_ET\n"
@@ -1919,151 +1919,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr ""
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr ""
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr ""
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr ""
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr ""
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr ""
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr ""
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr ""
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr ""
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr ""
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr ""
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr ""
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr ""
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr ""
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr ""
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr ""
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr ""
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr ""
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr ""
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "إعدادات التطبيق paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "الإنجليزية (الولايات المتحدة)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "العربية"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "اللغة الأفريقانية"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "البيلاروسية"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "البلغارية"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "اللغة الكتالونية"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "التشيكية"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "الدانماركية"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "الألمانية"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "اليونانية"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "الإنجليزية (المملكة المتحدة)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "الإسبانية"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "الفنلندية"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "الفرنسية"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "المجرية"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "الإيطالية"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "اليابانية"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "اللوكسمبرجية"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "النرويجية"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "الهولندية"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "البولندية"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "البرتغالية (البرازيل)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "البرتغالية"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "الرومانية"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "الروسية"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "السلوفاكية"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "السلوفانية"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "الصربية"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "السويدية"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "التركية"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "الأوكرانية"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "الصينية المبسطة"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Belarusian\n"
"Language: be_BY\n"
@@ -1919,151 +1919,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Англійская (ЗША)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Беларуская"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Чэшская"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Дацкая"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Нямецкая"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Англійская (Вялікабрытанія)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Іспанская"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Французская"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Італьянская"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Люксембургская"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Нідэрландская"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Польская"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Партугальская (Бразілія)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Партугальская"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Румынская"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Руская"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Славенская"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Сербская"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Шведская"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Турэцкая"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Кітайская спрошчаная"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"Language: bg_BG\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "настройки на софтуера"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Английски (САЩ)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Арабски"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Африканс"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Беларуски"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Български"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Каталунски"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Чешки"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Датски"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Немски"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Гръцки"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Английски (Великобритания)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Испански"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Финландски"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Френски"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Унгарски"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Италиански"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Японски"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Корейски"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Люксембургски"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Норвежки"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Холандски"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Полски"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Португалски (Бразилия)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Португалски"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Румънски"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Руски"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Словашки"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Словенски"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Сръбски"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Шведски"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Турски"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Украински"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Китайски опростен"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Китайски традиционен"
+41 -41
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Catalan\n"
"Language: ca_ES\n"
@@ -1914,157 +1914,157 @@ msgstr "Estableix l'idioma de sortida de LLM"
#: paperless/models.py:370
msgid "Sets the LLM timeout in seconds"
msgstr "Estableix temps d'espera en segons"
msgstr ""
#: paperless/models.py:376
msgid "paperless application settings"
msgstr "configuració de l'aplicació paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Anglès (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Àrab"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Africà"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Bielorús"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Búlgar"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Català"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Txec"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Danès"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Alemany"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Grec"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Anglès (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Espanyol"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Persa"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finès"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Francès"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Hongarès"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonesi"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italià"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Japonès"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Coreà"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxemburguès"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Noruec"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Holandès"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polonès"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portuguès (Brasil)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portuguès"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Romanès"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Rus"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Eslovac"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Eslovè"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbi"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Suec"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turc"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ucranià"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnamita"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Xinès Simplificat"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Xinès tradicional"
@@ -2386,7 +2386,7 @@ msgstr "uid"
#: paperless_mail/models.py:342
msgid "uid validity"
msgstr "validació uid"
msgstr ""
#: paperless_mail/models.py:350
msgid "subject"
+74 -74
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"Language: cs_CZ\n"
@@ -261,11 +261,11 @@ msgstr ""
#: documents/models.py:331
msgid "version label"
msgstr "označení verze"
msgstr ""
#: documents/models.py:335
msgid "Optional short label for a document version."
msgstr "Volitelné krátké označení verze dokumentu."
msgstr ""
#: documents/models.py:340 documents/models.py:851 documents/models.py:905
#: documents/models.py:1892
@@ -582,7 +582,7 @@ msgstr "filtrovací pravidla"
#: documents/models.py:679 documents/models.py:934
msgid "Pending"
msgstr "Čekající"
msgstr ""
#: documents/models.py:680 documents/models.py:757
msgid "Started"
@@ -602,7 +602,7 @@ msgstr ""
#: documents/models.py:686
msgid "Consume File"
msgstr "Zpracování souboru"
msgstr "Soubor zpracování"
#: documents/models.py:687
msgid "Train Classifier"
@@ -610,7 +610,7 @@ msgstr "Trénovací klasifikátor"
#: documents/models.py:688
msgid "Sanity Check"
msgstr "Kontrola zdraví"
msgstr ""
#: documents/models.py:689
msgid "Index Optimize"
@@ -622,31 +622,31 @@ msgstr "Načíst poštu"
#: documents/models.py:691
msgid "LLM Index"
msgstr "Index LLM"
msgstr ""
#: documents/models.py:692
msgid "Empty Trash"
msgstr "Vyprázdnění koše"
msgstr ""
#: documents/models.py:693
msgid "Check Workflows"
msgstr "Kontrola postupů"
msgstr ""
#: documents/models.py:694
msgid "Bulk Update"
msgstr "Hromadná aktualizace"
msgstr ""
#: documents/models.py:695
msgid "Reprocess Document"
msgstr "Opětovné zpracování dokumentu"
msgstr ""
#: documents/models.py:696
msgid "Build Share Link"
msgstr "Sestavení odkazu ke sdílení"
msgstr ""
#: documents/models.py:697
msgid "Bulk Delete"
msgstr "Hromadné smazání"
msgstr ""
#: documents/models.py:706 documents/models.py:1277
msgid "Scheduled"
@@ -662,19 +662,19 @@ msgstr ""
#: documents/models.py:709
msgid "Folder Consume"
msgstr "Zpracování složky"
msgstr ""
#: documents/models.py:710
msgid "Email Consume"
msgstr "Zpracování e-mailu"
msgstr ""
#: documents/models.py:711
msgid "System"
msgstr "Systém"
msgstr ""
#: documents/models.py:712
msgid "Manual"
msgstr "Ruční"
msgstr ""
#: documents/models.py:718
msgid "Task ID"
@@ -750,7 +750,7 @@ msgstr ""
#: documents/models.py:806
msgid "Tasks"
msgstr "Úlohy"
msgstr ""
#: documents/models.py:836
msgid "Note for the document"
@@ -802,7 +802,7 @@ msgstr ""
#: documents/models.py:937
msgid "Failed"
msgstr "Selhané"
msgstr ""
#: documents/models.py:984
msgid "size (bytes)"
@@ -826,7 +826,7 @@ msgstr ""
#: documents/models.py:1017
msgid "share link bundles"
msgstr "balíčky sdílení odkazů"
msgstr ""
#: documents/models.py:1020
#, python-format
@@ -971,7 +971,7 @@ msgstr "má tento typ dokumentu"
#: documents/models.py:1377
msgid "has one of these document types"
msgstr "má jeden z následujících typů dokumentů"
msgstr ""
#: documents/models.py:1384
msgid "does not have these document type(s)"
@@ -1342,7 +1342,7 @@ msgstr "spuštění pracovních postupů"
#: documents/serialisers.py:2763 documents/views.py:298 documents/views.py:2541
#: paperless_mail/serialisers.py:155
msgid "Insufficient permissions."
msgstr "Nedostatečná oprávnění."
msgstr ""
#: documents/serialisers.py:710
msgid "Invalid color."
@@ -1655,7 +1655,7 @@ msgstr ""
#: documents/views.py:4512
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr "Nedostatečná oprávnění ke sdílení dokumentu %(id)s."
msgstr ""
#: documents/views.py:4558
msgid "Bundle is already being processed."
@@ -1779,7 +1779,7 @@ msgstr "Nastaví režim OCR"
#: paperless/models.py:131
msgid "Controls archive file generation"
msgstr "Ovládá generování archivních souborů"
msgstr ""
#: paperless/models.py:139
msgid "Sets image DPI fallback value"
@@ -1867,7 +1867,7 @@ msgstr "Nastaví mapování čárových kódů na štítky"
#: paperless/models.py:285
msgid "Enables splitting on tag barcodes"
msgstr "Umožňuje rozdělení podle čárových kódů štítků"
msgstr ""
#: paperless/models.py:294
msgid "Enables AI features"
@@ -1875,197 +1875,197 @@ msgstr ""
#: paperless/models.py:300
msgid "Sets the LLM embedding backend"
msgstr "Nastaví backend pro vkládání LLM"
msgstr ""
#: paperless/models.py:308
msgid "Sets the LLM embedding model"
msgstr "Nastaví model vkládání LLM"
msgstr ""
#: paperless/models.py:315
msgid "Sets the LLM embedding endpoint, optional"
msgstr "Nastaví koncový bod vkládání LLM, volitelné"
msgstr ""
#: paperless/models.py:322
msgid "Sets the LLM embedding chunk size"
msgstr "Nastaví velikost bloků vkládání LLM"
msgstr ""
#: paperless/models.py:328
msgid "Sets the LLM context size"
msgstr "Nastaví velikost kontextu LLM"
msgstr ""
#: paperless/models.py:334
msgid "Sets the LLM backend"
msgstr "Nastaví backend LLM"
msgstr ""
#: paperless/models.py:342
msgid "Sets the LLM model"
msgstr "Nastaví model LLM"
msgstr ""
#: paperless/models.py:349
msgid "Sets the LLM API key"
msgstr "Nastaví klíč API LLM"
msgstr ""
#: paperless/models.py:356
msgid "Sets the LLM endpoint, optional"
msgstr "Nastaví koncový bod LLM, volitelné"
msgstr ""
#: paperless/models.py:363
msgid "Sets the LLM output language"
msgstr "Nastaví výstupní jazyk LLM"
msgstr ""
#: paperless/models.py:370
msgid "Sets the LLM timeout in seconds"
msgstr "Nastaví časový limit LLM v sekundách"
msgstr ""
#: paperless/models.py:376
msgid "paperless application settings"
msgstr "nastavení aplikace paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Angličtina (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arabština"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Afrikánština"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Běloruština"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Bulharština"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Katalánština"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Čeština"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Dánština"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Němčina"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Řečtina"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Angličtina (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Španělština"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Perština"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finština"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Francouzština"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Maďarština"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonéština"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italština"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Japonština"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Korejština"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Lucemburština"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norština"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Holandština"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polština"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugalština (Brazílie)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugalština"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Rumunština"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Ruština"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Slovenština"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slovinština"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Srbština"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Švédština"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turečtina"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ukrajinština"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnamština"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Čínština (zjednodušená)"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Čínština (tradiční)"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Language: da_DK\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "paperless-applikationsindstillinger"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Engelsk (USA)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Afrikaans"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Hviderussisk"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Bulgarsk"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Katalansk"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Tjekkisk"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Dansk"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Tysk"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Græsk"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Engelsk (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Spansk"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finsk"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Fransk"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Ungarsk"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italiensk"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Japansk"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Koreansk"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxemburgsk"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norsk"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Hollandsk"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polsk"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugisisk (Brasilien)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugisisk"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Romansk"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Russisk"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Slovakisk"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slovensk"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbisk"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Svensk"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Tyrkisk"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ukrainsk"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Kinesisk, forenklet"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Kinesisk, traditionelt"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: German, Switzerland\n"
"Language: de_CH\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "Paperless-ngx-Anwendungseinstellungen"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Englisch (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arabisch"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Afrikanisch"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Belarussisch"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Bulgarisch"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Katalanisch"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Tschechisch"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Dänisch"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Deutsch"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Griechisch"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Englisch (UK)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Spanisch"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Persisch"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finnisch"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Französisch"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Ungarisch"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonesisch"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italienisch"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Japanisch"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Koreanisch"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxemburgisch"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norwegisch"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Niederländisch"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polnisch"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugiesisch (Brasilien)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugiesisch"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Rumänisch"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Russisch"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Slowakisch"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slowenisch"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbisch"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Schwedisch"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Türkisch"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ukrainisch"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnamesisch"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Chinesisch (vereinfacht)"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Chinesisch (traditionell)"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -1920,151 +1920,151 @@ msgstr "Legt das LLM-Zeitlimit in Sekunden fest"
msgid "paperless application settings"
msgstr "Paperless-ngx-Anwendungseinstellungen"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Englisch (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arabisch"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Afrikanisch"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Belarussisch"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Bulgarisch"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Katalanisch"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Tschechisch"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Dänisch"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Deutsch"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Griechisch"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Englisch (UK)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Spanisch"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Persisch"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finnisch"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Französisch"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Ungarisch"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonesisch"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italienisch"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Japanisch"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Koreanisch"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxemburgisch"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norwegisch"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Niederländisch"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polnisch"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugiesisch (Brasilien)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugiesisch"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Rumänisch"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Russisch"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Slowakisch"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slowenisch"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbisch"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Schwedisch"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Türkisch"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ukrainisch"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnamesisch"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Chinesisch (vereinfacht)"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Chinesisch (traditionell)"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"Language: el_GR\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "ρυθμίσεις εφαρμογής paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Αγγλικά (ΗΠΑ)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Αραβικά"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Αφρικανικά"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Λευκορωσικά"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Βουλγαρικά"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Καταλανικά"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Τσέχικα"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Δανέζικα"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Γερμανικά"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Ελληνικά"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Αγγλικά (Ηνωμένο Βασίλειο)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Ισπανικά"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Φινλανδικά"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Γαλλικά"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Ουγγρικά"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Ιταλικά"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Ιαπωνικά"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Κορεάτικα"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Λουξεμβουργικά"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Νορβηγικά"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Ολλανδικά"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Πολωνικά"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Πορτογαλικά (Βραζιλίας)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Πορτογαλικά"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Ρουμάνικα"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Ρωσικά"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Σλοβακικά"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Σλοβενικά"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Σερβικά"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Σουηδικά"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Τούρκικα"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ουκρανικά"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Κινέζικα Απλοποιημένα"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Κινεζικά Παραδοσιακά"
+57 -57
View File
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-27 19:34+0000\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -21,39 +21,39 @@ msgstr ""
msgid "Documents"
msgstr ""
#: documents/filters.py:472
#: documents/filters.py:470
msgid "Value must be valid JSON."
msgstr ""
#: documents/filters.py:491
#: documents/filters.py:489
msgid "Invalid custom field query expression"
msgstr ""
#: documents/filters.py:501
#: documents/filters.py:499
msgid "Invalid expression list. Must be nonempty."
msgstr ""
#: documents/filters.py:522
#: documents/filters.py:520
msgid "Invalid logical operator {op!r}"
msgstr ""
#: documents/filters.py:536
#: documents/filters.py:534
msgid "Maximum number of query conditions exceeded."
msgstr ""
#: documents/filters.py:600
#: documents/filters.py:598
msgid "{name!r} is not a valid custom field."
msgstr ""
#: documents/filters.py:637
#: documents/filters.py:635
msgid "{data_type} does not support query expr {expr!r}."
msgstr ""
#: documents/filters.py:752 documents/models.py:136
#: documents/filters.py:750 documents/models.py:136
msgid "Maximum nesting depth exceeded."
msgstr ""
#: documents/filters.py:1094
#: documents/filters.py:1067
msgid "Custom field not found"
msgstr ""
@@ -1352,7 +1352,7 @@ msgid "workflow runs"
msgstr ""
#: documents/serialisers.py:522 documents/serialisers.py:874
#: documents/serialisers.py:2763 documents/views.py:299 documents/views.py:2553
#: documents/serialisers.py:2763 documents/views.py:298 documents/views.py:2541
#: paperless_mail/serialisers.py:155
msgid "Insufficient permissions."
msgstr ""
@@ -1393,7 +1393,7 @@ msgstr ""
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2849 documents/views.py:4512
#: documents/serialisers.py:2849 documents/views.py:4500
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1661,36 +1661,36 @@ msgstr ""
msgid "Unable to parse URI {value}"
msgstr ""
#: documents/views.py:292 documents/views.py:2550
#: documents/views.py:291 documents/views.py:2538
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:1562
#: documents/views.py:1556
msgid "Invalid AI configuration."
msgstr ""
#: documents/views.py:1571
#: documents/views.py:1565
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:2375 documents/views.py:2696
#: documents/views.py:2363 documents/views.py:2684
msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr ""
#: documents/views.py:4524
#: documents/views.py:4512
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:4570
#: documents/views.py:4558
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:4631
#: documents/views.py:4619
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:4641
#: documents/views.py:4629
msgid "The share link bundle is unavailable."
msgstr ""
@@ -1946,151 +1946,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr ""
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr ""
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr ""
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr ""
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr ""
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr ""
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr ""
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr ""
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr ""
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr ""
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr ""
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr ""
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr ""
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr ""
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr ""
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr ""
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr ""
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr ""
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr ""
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -1920,151 +1920,151 @@ msgstr "Establece el tiempo de espera de LLM en segundos"
msgid "paperless application settings"
msgstr "Ajustes de la aplicación paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Inglés (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Árabe"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Africano"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Bielorruso"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Búlgaro"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Catalán"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Checo"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Danés"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Alemán"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Griego"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Inglés (Gran Bretaña)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Español"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Persa"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finlandés"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Francés"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Húngaro"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonesio"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italiano"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Japonés"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Coreano"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxemburgués"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Noruego"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Alemán"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polaco"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugués (Brasil)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugués"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Rumano"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Ruso"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Eslovaco"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Esloveno"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbio"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Sueco"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turco"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ucraniano"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnamita"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Chino simplificado"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Chino tradicional"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Estonian\n"
"Language: et_EE\n"
@@ -1919,151 +1919,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr ""
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr ""
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr ""
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr ""
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr ""
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr ""
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr ""
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr ""
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr ""
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr ""
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr ""
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr ""
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr ""
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr ""
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr ""
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr ""
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr ""
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr ""
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr ""
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Persian\n"
"Language: fa_IR\n"
@@ -1919,151 +1919,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "تنظیمات برنامه بدون کاغذ"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "انگلیسی (ایالات متحده)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "عربی"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "افریکان"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "وابسته به بلروس"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "وابسته به بلغاری"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "کاتالان"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "وابسته به چک"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "وابسته به دانمارکی"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "آلمانی"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "یونانی"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "انگلیسی (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "اسپانیایی"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "فارسی"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "فنلاندی"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "فرانسوی"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "مجارستانی"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "ایتالیایی"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "وابسته به ژاپنی"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "کره ای"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "وابسته به لوکس"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "نروژی"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "هلندی"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "صیقل دادن"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "پرتغالی (برزیل)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "وابسته به پرتغالی"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "وابسته به رومانی"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "روسی"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "لگن"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "وابسته به اسلوونی"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "صرب"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "سوئدی"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "ترکی"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "وابسته به اوکراینی"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "چینی ساده شد"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "چینی سنتی"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"Language: fi_FI\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Englanti (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arabialainen"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "valkovenäjä"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Katalaani"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Tšekki"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Tanska"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Saksa"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Kreikka"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Englanti (US)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Espanja"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Suomi"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Ranska"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italia"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxemburg"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norja"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Hollanti"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "puola"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "portugali (Brasilia)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "portugali"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "romania"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "venäjä"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Slovakia"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slovenia"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbia"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "ruotsi"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turkki"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ukraina"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Kiina (yksinkertaistettu)"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -1919,151 +1919,151 @@ msgstr "Définit le délai d'attente du LLM en secondes"
msgid "paperless application settings"
msgstr "paramètres de l'application paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Anglais (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arabe"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Afrikaans"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Biélorusse"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Bulgare"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Catalan"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Tchèque"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Danois"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Allemand"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Grec"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Anglais (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Espagnol"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Persan"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finnois"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Français"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Hongrois"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonésien"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italien"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Japonais"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Coréen"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxembourgeois"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norvégien"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Néerlandais"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polonais"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugais (Brésil)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugais"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Roumain"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Russe"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Solvaque"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slovène"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbe"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Suédois"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turc"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ukrainien"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnamien"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Chinois simplifié"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Chinois traditionnel"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
"Language: he_IL\n"
@@ -1921,151 +1921,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "הגדרות יישום paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "אנגלית (ארה״ב)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "ערבית"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "אפריקאנס"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "בלרוסית"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "בולגרית"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "קטלאנית"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "צ'כית"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "דנית"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "גרמנית"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "יוונית"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "אנגלית (בריטניה)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "ספרדית"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "פרסית"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "פינית"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "צרפתית"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "הונגרית"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "אינדונזית"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "איטלקית"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "יפנית"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "קוריאנית"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "לוקסמבורגית"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "נורווגית"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "הולנדית"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "פולנית"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "פורטוגלית ברזילאית"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "פורטוגלית"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "רומנית"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "רוסית"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "סלובקית"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "סלובנית"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "סרבית"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "שוודית"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "טורקית"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "אוקראינית"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "ווייטנאמית"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "סינית מפושטת"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "סינית מסורתית"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Hindi\n"
"Language: hi_IN\n"
@@ -1919,151 +1919,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr ""
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr ""
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr ""
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr ""
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr ""
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr ""
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr ""
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr ""
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr ""
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr ""
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr ""
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr ""
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr ""
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr ""
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr ""
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr ""
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr ""
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr ""
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr ""
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Croatian\n"
"Language: hr_HR\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "postavke paperless aplikacije"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Engleski (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arapski"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "afrički"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Bjeloruski"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "bugarski"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "katalonski"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Češki"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Danski"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Njemački"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "grčki"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Engleski (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Španjolski"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "perzijski"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "finski"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Francuski"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "mađarski"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Iidonezijski"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Talijanski"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "japanski"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "koreanski"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luksemburški"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "norveški"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Nizozemski"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Poljski"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugalski (Brazil)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugalski"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Rumunjski"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Ruski"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "slovački"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slovenski"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Srpski"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Švedski"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turski"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "ukrajinski"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "vijetnamski"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Pojednostavljeni kineski"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Kineski tradicionalni"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"Language: hu_HU\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "paperless alkalmazás beállítások"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Angol (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arab"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Afrikai"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Fehérorosz"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Bolgár"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Katalán"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Cseh"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Dán"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Német"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Görög"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Angol (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Spanyol"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Perzsa"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finn"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Francia"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Magyar"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonéz"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Olasz"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Japán"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Koreai"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luxemburgi"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norvég"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Holland"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Lengyel"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugál (Brazília)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugál"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Román"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Orosz"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Szlovák"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Szlovén"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Szerb"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Svéd"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Török"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ukrán"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnámi"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Kínai egyszerűsített"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Tradicionális kínai"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Indonesian\n"
"Language: id_ID\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "pengaturan aplikasi paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Inggris (AS)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arab"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Bahasa Afrika"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Belarusia"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Bahasa Bulgaria"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Katalan"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Bahasa Ceko"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Denmark"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Jerman"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Yunani"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Inggris (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Spanyol"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Persia"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finlandia"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Prancis"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Hungaria"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonesia"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italia"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Bahasa Jepang"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Bahasa Korea"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Luksemburg"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norwegia"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Belanda"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polandia"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugis (Brasil)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugis"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Rumania"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Rusia"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Slovakia"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slovenia"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbia"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Swedia"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turki"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ukraina"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnam"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Mandarin Sederhana"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Tionghoa Tradisional"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
@@ -1920,151 +1920,151 @@ msgstr "Impostare il timeout dellLLM in secondi"
msgid "paperless application settings"
msgstr "impostazioni dell'applicazione paperless"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Inglese (US)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "Arabo"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "Afrikaans"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Bielorusso"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "Bulgaro"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "Catalano"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Ceco"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Danese"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Tedesco"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "Greco"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Inglese (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Spagnolo"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "Persiano"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "Finlandese"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Francese"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "Ungherese"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr "Indonesiano"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italiano"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "Giapponese"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "Coreano"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Lussemburghese"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "Norvegese"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Olandese"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polacco"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portoghese (Brasile)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portoghese"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Rumeno"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Russo"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "Slovacco"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Sloveno"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbo"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Svedese"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Turco"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "Ucraino"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "Vietnamita"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Cinese semplificato"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "Cinese tradizionale"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Language: ja_JP\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "Paperless アプリケーションの設定"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "英語 (米国)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "アラビア語"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "アフリカーンス語"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "ベラルーシ語"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "ブルガリア語"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "カタロニア語"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "チェコ語"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "デンマーク語"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "ドイツ語"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "ギリシャ語"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "英語 (英国)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "スペイン語"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "フィンランド語"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "フランス語"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "ハンガリー語"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "イタリア語"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "日本語"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "韓国語"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "ルクセンブルク語"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "ノルウェー語"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "オランダ語"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "ポーランド語"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "ポルトガル語 (ブラジル)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "ポルトガル語"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "ルーマニア語"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "ロシア語"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "スロバキア語"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "スロベニア語"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "セルビア語"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "スウェーデン語"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "トルコ語"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "ウクライナ語"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "中国語 (簡体字)"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "中国語 (繁体)"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Language: ko_KR\n"
@@ -1920,151 +1920,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "페이퍼리스 애플리케이션 설정"
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "영어 (미국)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr "아랍어"
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr "아프리칸스어"
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "벨라루스어"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr "불가리어"
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr "카탈로니아어"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "체코어"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "덴마크어"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "독일어"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr "그리스어"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "영어 (영국)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "스페인어"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr "페르시아어"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr "핀란드어"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "프랑스어"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr "헝가리어"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "이탈리아어"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr "일본어"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr "한국어"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "룩셈부르크어"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr "노르웨이어"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "네덜란드어"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "폴란드어"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "포르투갈어 (브라질)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "포르투갈어"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "루마니아어"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "러시아어"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr "슬로바키아어"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "슬로베니아어"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "세르비아어"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "스웨덴어"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "튀르키예어"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr "우크라이나어"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr "베트남어"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "중국어 간체"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr "중국어 번체"
+39 -39
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
"PO-Revision-Date: 2026-07-25 07:14\n"
"POT-Creation-Date: 2026-07-23 19:21+0000\n"
"PO-Revision-Date: 2026-07-23 19:23\n"
"Last-Translator: \n"
"Language-Team: Luxembourgish\n"
"Language: lb_LU\n"
@@ -1919,151 +1919,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:556
#: paperless/settings/__init__.py:545
msgid "English (US)"
msgstr "Englesch (USA)"
#: paperless/settings/__init__.py:557
#: paperless/settings/__init__.py:546
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:558
#: paperless/settings/__init__.py:547
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:559
#: paperless/settings/__init__.py:548
msgid "Belarusian"
msgstr "Belarusesch"
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:549
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:550
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:551
msgid "Czech"
msgstr "Tschechesch"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:552
msgid "Danish"
msgstr "Dänesch"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:553
msgid "German"
msgstr "Däitsch"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:554
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:555
msgid "English (GB)"
msgstr "Englesch (GB)"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:556
msgid "Spanish"
msgstr "Spuenesch"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:557
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:558
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:559
msgid "French"
msgstr "Franséisch"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:560
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:561
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:562
msgid "Italian"
msgstr "Italienesch"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:563
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:564
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:565
msgid "Luxembourgish"
msgstr "Lëtzebuergesch"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:566
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:567
msgid "Dutch"
msgstr "Hollännesch"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:568
msgid "Polish"
msgstr "Polnesch"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:569
msgid "Portuguese (Brazil)"
msgstr "Portugisesch (Brasilien)"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:570
msgid "Portuguese"
msgstr "Portugisesch"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:571
msgid "Romanian"
msgstr "Rumänesch"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:572
msgid "Russian"
msgstr "Russesch"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:573
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:574
msgid "Slovenian"
msgstr "Slowenesch"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:575
msgid "Serbian"
msgstr "Serbesch"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:576
msgid "Swedish"
msgstr "Schwedesch"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:577
msgid "Turkish"
msgstr "Tierkesch"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:578
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:579
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:580
msgid "Chinese Simplified"
msgstr "Chinesesch (Vereinfacht)"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:581
msgid "Chinese Traditional"
msgstr ""

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