mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-08 09:07:13 +00:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a8df9dca0 | ||
|
|
f1e85fff3e | ||
|
|
f549201b7c | ||
|
|
26efad77ac | ||
|
|
901dd4be93 | ||
|
|
8f73514fbb | ||
|
|
f30a65f440 | ||
|
|
2136659e2b | ||
|
|
26baf52a78 | ||
|
|
3143d1936a | ||
|
|
1a9b08394c | ||
|
|
ae3d8680fa |
No files matched your search
@@ -111,7 +111,7 @@ jobs:
|
|||||||
timeout-minutes: 12
|
timeout-minutes: 12
|
||||||
uses: $/.github/actions/apt-install
|
uses: $/.github/actions/apt-install
|
||||||
with:
|
with:
|
||||||
packages: unpaper tesseract-ocr imagemagick ghostscript poppler-utils
|
packages: unpaper tesseract-ocr imagemagick ghostscript poppler-utils qpdf
|
||||||
- name: Configure ImageMagick
|
- name: Configure ImageMagick
|
||||||
run: |
|
run: |
|
||||||
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-7/policy.xml
|
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-7/policy.xml
|
||||||
|
|||||||
+6
-18
@@ -1315,29 +1315,17 @@ valid crontab(5) expression describing when to run.
|
|||||||
|
|
||||||
#### [`PAPERLESS_CONVERT_MEMORY_LIMIT=<num>`](#PAPERLESS_CONVERT_MEMORY_LIMIT) {#PAPERLESS_CONVERT_MEMORY_LIMIT}
|
#### [`PAPERLESS_CONVERT_MEMORY_LIMIT=<num>`](#PAPERLESS_CONVERT_MEMORY_LIMIT) {#PAPERLESS_CONVERT_MEMORY_LIMIT}
|
||||||
|
|
||||||
: On smaller systems, or even in the case of Very Large Documents, the
|
!!! warning
|
||||||
consumer may explode, complaining about how it's "unable to extend
|
|
||||||
pixel cache". In such cases, try setting this to a reasonably low
|
|
||||||
value, like 32. The default is to use whatever is necessary to do
|
|
||||||
everything without writing to disk, and units are in megabytes.
|
|
||||||
|
|
||||||
For more information on how to use this value, you should search the
|
Deprecated and has no effect, since PDF thumbnails no longer use
|
||||||
web for "MAGICK_MEMORY_LIMIT".
|
ImageMagick. It will be removed in a future release.
|
||||||
|
|
||||||
Defaults to 0, which disables the limit.
|
|
||||||
|
|
||||||
#### [`PAPERLESS_CONVERT_TMPDIR=<path>`](#PAPERLESS_CONVERT_TMPDIR) {#PAPERLESS_CONVERT_TMPDIR}
|
#### [`PAPERLESS_CONVERT_TMPDIR=<path>`](#PAPERLESS_CONVERT_TMPDIR) {#PAPERLESS_CONVERT_TMPDIR}
|
||||||
|
|
||||||
: Similar to the memory limit, if you've got a small system and your
|
!!! warning
|
||||||
OS mounts /tmp as tmpfs, you should set this to a path that's on a
|
|
||||||
physical disk, like /home/your_user/tmp or something. ImageMagick
|
|
||||||
will use this as scratch space when crunching through very large
|
|
||||||
documents.
|
|
||||||
|
|
||||||
For more information on how to use this value, you should search the
|
Deprecated and has no effect, since PDF thumbnails no longer use
|
||||||
web for "MAGICK_TMPDIR".
|
ImageMagick. It will be removed in a future release.
|
||||||
|
|
||||||
Default is none, which disables the temporary directory.
|
|
||||||
|
|
||||||
#### [`PAPERLESS_APPS=<string>`](#PAPERLESS_APPS) {#PAPERLESS_APPS}
|
#### [`PAPERLESS_APPS=<string>`](#PAPERLESS_APPS) {#PAPERLESS_APPS}
|
||||||
|
|
||||||
|
|||||||
+4
-7
@@ -177,12 +177,12 @@ to a positive number to enable polling and disable native filesystem notificatio
|
|||||||
- `pkg-config` for mysqlclient (python dependency)
|
- `pkg-config` for mysqlclient (python dependency)
|
||||||
- `fonts-liberation` for generating thumbnails for plain text
|
- `fonts-liberation` for generating thumbnails for plain text
|
||||||
files
|
files
|
||||||
- `imagemagick` >= 6 for PDF conversion
|
- `imagemagick` >= 6 for image alpha handling
|
||||||
- `gnupg` for decrypting GPG-encrypted email
|
- `gnupg` for decrypting GPG-encrypted email
|
||||||
- `libpq-dev` for PostgreSQL
|
- `libpq-dev` for PostgreSQL
|
||||||
- `libmagic-dev` for mime type detection
|
- `libmagic-dev` for mime type detection
|
||||||
- `mariadb-client` for MariaDB compile time
|
- `mariadb-client` for MariaDB compile time
|
||||||
- `poppler-utils` for barcode detection
|
- `poppler-utils` for thumbnail generation and barcode detection
|
||||||
|
|
||||||
Use this list for your preferred package management:
|
Use this list for your preferred package management:
|
||||||
|
|
||||||
@@ -416,11 +416,8 @@ to a positive number to enable polling and disable native filesystem notificatio
|
|||||||
You may need to change the path in the files. Example:
|
You may need to change the path in the files. Example:
|
||||||
`ExecStart=/opt/paperless/.local/bin/celery --app paperless worker --loglevel INFO`
|
`ExecStart=/opt/paperless/.local/bin/celery --app paperless worker --loglevel INFO`
|
||||||
|
|
||||||
12. Configure ImageMagick to allow processing of PDF documents and disable
|
12. Harden ImageMagick by disabling formats that Paperless-ngx does not use.
|
||||||
formats that Paperless-ngx does not use. Most distributions disable PDF
|
PDF processing is not needed and should stay disabled.
|
||||||
processing by default, since PDF documents can contain malware. If you
|
|
||||||
don't enable it, Paperless-ngx will fall back to Ghostscript for certain
|
|
||||||
steps such as thumbnail generation.
|
|
||||||
|
|
||||||
Configure the active ImageMagick policy file (commonly
|
Configure the active ImageMagick policy file (commonly
|
||||||
`/etc/ImageMagick-6/policy.xml` or `/etc/ImageMagick-7/policy.xml`) and
|
`/etc/ImageMagick-6/policy.xml` or `/etc/ImageMagick-7/policy.xml`) and
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ PAPERLESS_SECRET_KEY=change-me
|
|||||||
#PAPERLESS_OCR_ROTATE_PAGES=true
|
#PAPERLESS_OCR_ROTATE_PAGES=true
|
||||||
#PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD=12.0
|
#PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD=12.0
|
||||||
#PAPERLESS_OCR_USER_ARGS={}
|
#PAPERLESS_OCR_USER_ARGS={}
|
||||||
#PAPERLESS_CONVERT_MEMORY_LIMIT=0
|
|
||||||
#PAPERLESS_CONVERT_TMPDIR=/var/tmp/paperless
|
|
||||||
|
|
||||||
# Software tweaks
|
# Software tweaks
|
||||||
|
|
||||||
|
|||||||
@@ -8525,7 +8525,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">54</context>
|
<context context-type="linenumber">54</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">{VAR_PLURAL, plural, =1 {1 Vorschlag unten verfügbar} other {<x id="INTERPOLATION"/> Vorschläge unten verfügbar}}</target>
|
<target state="needs-translation">{VAR_PLURAL, plural, =1 {1 suggestion available below} other {<x id="INTERPOLATION"/> suggestions available below}}</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4169875112244042673" datatype="html" approved="yes">
|
<trans-unit id="4169875112244042673" datatype="html" approved="yes">
|
||||||
<source>Suggestion options</source>
|
<source>Suggestion options</source>
|
||||||
|
|||||||
@@ -9219,7 +9219,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-detail/document-barcodes/document-barcodes.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-barcodes/document-barcodes.component.html</context>
|
||||||
<context context-type="linenumber">40,41</context>
|
<context context-type="linenumber">40,41</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Copia contenuto</target>
|
<target state="needs-translation">Copy content</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2266163016683537825" datatype="html">
|
<trans-unit id="2266163016683537825" datatype="html">
|
||||||
<source>of <x id="INTERPOLATION" equiv-text="{{previewNumPages()}}"/></source>
|
<source>of <x id="INTERPOLATION" equiv-text="{{previewNumPages()}}"/></source>
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
+161
-98
@@ -1,8 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -68,58 +68,6 @@ def get_supported_file_extensions() -> set[str]:
|
|||||||
return extensions
|
return extensions
|
||||||
|
|
||||||
|
|
||||||
def run_convert(
|
|
||||||
input_file,
|
|
||||||
output_file,
|
|
||||||
*,
|
|
||||||
density=None,
|
|
||||||
scale=None,
|
|
||||||
alpha=None,
|
|
||||||
strip=False,
|
|
||||||
trim=False,
|
|
||||||
type=None,
|
|
||||||
depth=None,
|
|
||||||
auto_orient=False,
|
|
||||||
use_cropbox=False,
|
|
||||||
extra=None,
|
|
||||||
logging_group=None,
|
|
||||||
) -> None:
|
|
||||||
environment = os.environ.copy()
|
|
||||||
if settings.CONVERT_MEMORY_LIMIT:
|
|
||||||
# MAGICK_MEMORY_LIMIT sets the maximum amount of RAM the pixel cache can use.
|
|
||||||
# MAGICK_MAP_LIMIT sets the maximum amount of memory-mapped I/O allowed.
|
|
||||||
#
|
|
||||||
# For large-format documents ImageMagick will hit the RAM limit and
|
|
||||||
# immediately try to "map" the remaining data. If MAGICK_MAP_LIMIT isn't
|
|
||||||
# also set, the process may trigger an OOM kill because the default
|
|
||||||
# system/policy map limit is often too restrictive for these massive bitmaps.
|
|
||||||
environment["MAGICK_MEMORY_LIMIT"] = settings.CONVERT_MEMORY_LIMIT
|
|
||||||
environment["MAGICK_MAP_LIMIT"] = settings.CONVERT_MEMORY_LIMIT
|
|
||||||
if settings.CONVERT_TMPDIR:
|
|
||||||
environment["MAGICK_TMPDIR"] = settings.CONVERT_TMPDIR
|
|
||||||
|
|
||||||
args = [settings.CONVERT_BINARY]
|
|
||||||
args += ["-density", str(density)] if density else []
|
|
||||||
args += ["-scale", str(scale)] if scale else []
|
|
||||||
args += ["-alpha", str(alpha)] if alpha else []
|
|
||||||
args += ["-strip"] if strip else []
|
|
||||||
args += ["-trim"] if trim else []
|
|
||||||
args += ["-type", str(type)] if type else []
|
|
||||||
args += ["-depth", str(depth)] if depth else []
|
|
||||||
args += ["-auto-orient"] if auto_orient else []
|
|
||||||
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})
|
|
||||||
|
|
||||||
try:
|
|
||||||
run_subprocess(args, environment, logger)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
raise ParseError(f"Convert failed at {args}") from e
|
|
||||||
except Exception as e: # pragma: no cover
|
|
||||||
raise ParseError("Unknown error running convert") from e
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_thumbnail() -> Path:
|
def get_default_thumbnail() -> Path:
|
||||||
"""
|
"""
|
||||||
Returns the path to a generic thumbnail
|
Returns the path to a generic thumbnail
|
||||||
@@ -127,46 +75,168 @@ def get_default_thumbnail() -> Path:
|
|||||||
return (Path(__file__).parent / "resources" / "document.webp").resolve()
|
return (Path(__file__).parent / "resources" / "document.webp").resolve()
|
||||||
|
|
||||||
|
|
||||||
def make_thumbnail_from_pdf_gs_fallback(in_path, temp_dir, logging_group=None) -> Path:
|
_THUMBNAIL_MAX_WIDTH = 500
|
||||||
out_path: Path = Path(temp_dir) / "convert_gs.webp"
|
_THUMBNAIL_MAX_HEIGHT = 5000
|
||||||
|
# Used only when the page geometry cannot be read
|
||||||
|
_THUMBNAIL_FALLBACK_DPI = 150
|
||||||
|
# Applied before supersampling, so tiny pages are not enlarged
|
||||||
|
_THUMBNAIL_MAX_DPI = 300
|
||||||
|
# Rendering at a multiple and downsampling keeps text crisper
|
||||||
|
_THUMBNAIL_SUPERSAMPLE = 2
|
||||||
|
|
||||||
# if convert fails, fall back to extracting
|
|
||||||
# the first PDF page as a PNG using Ghostscript
|
def rasterize_pdf_page_to_png(
|
||||||
logger.warning(
|
in_path: Path,
|
||||||
"Thumbnail generation with ImageMagick failed, falling back "
|
out_path: Path,
|
||||||
"to ghostscript. Check your /etc/ImageMagick-x/policy.xml!",
|
*,
|
||||||
extra={"group": logging_group},
|
dpi: int,
|
||||||
)
|
logging_group=None,
|
||||||
# Ghostscript doesn't handle WebP outputs
|
) -> None:
|
||||||
gs_out_path: Path = Path(temp_dir) / "gs_out.png"
|
"""
|
||||||
cmd = [settings.GS_BINARY, "-q", "-sDEVICE=pngalpha", "-o", gs_out_path, in_path]
|
Rasterizes the first page of a PDF to a PNG with pdftoppm.
|
||||||
|
"""
|
||||||
|
# -singlefile drops the page number and -png appends ".png", so pass the
|
||||||
|
# path without its suffix
|
||||||
|
args = [
|
||||||
|
"pdftoppm",
|
||||||
|
"-f",
|
||||||
|
"1",
|
||||||
|
"-l",
|
||||||
|
"1",
|
||||||
|
"-r",
|
||||||
|
str(dpi),
|
||||||
|
"-png",
|
||||||
|
"-singlefile",
|
||||||
|
"-cropbox",
|
||||||
|
str(in_path),
|
||||||
|
str(out_path.with_suffix("")),
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.debug("Execute: " + " ".join(args), extra={"group": logging_group})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
run_subprocess(args, logger=logger)
|
||||||
run_subprocess(cmd, logger=logger)
|
except subprocess.CalledProcessError as e:
|
||||||
except subprocess.CalledProcessError as e:
|
raise ParseError(f"pdftoppm failed at {args}") from e
|
||||||
raise ParseError(f"Thumbnail (gs) failed at {cmd}") from e
|
except Exception as e: # pragma: no cover
|
||||||
# then run convert on the output from gs to make WebP
|
raise ParseError("Unknown error running pdftoppm") from e
|
||||||
run_convert(
|
|
||||||
density=300,
|
|
||||||
scale="500x5000>",
|
|
||||||
alpha="remove",
|
|
||||||
strip=True,
|
|
||||||
trim=False,
|
|
||||||
auto_orient=True,
|
|
||||||
input_file=gs_out_path,
|
|
||||||
output_file=out_path,
|
|
||||||
logging_group=logging_group,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
def encode_thumbnail_webp(
|
||||||
|
png_path: Path,
|
||||||
|
out_path: Path,
|
||||||
|
*,
|
||||||
|
supersample: int = 1,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Flattens alpha onto white, undoes supersampling, shrinks to fit and saves as WebP.
|
||||||
|
"""
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
try:
|
||||||
|
with Image.open(png_path) as im:
|
||||||
|
if im.mode in ("RGBA", "LA"):
|
||||||
|
flattened = Image.new("RGB", im.size, (255, 255, 255))
|
||||||
|
flattened.paste(im, mask=im.split()[-1])
|
||||||
|
else:
|
||||||
|
flattened = im.convert("RGB")
|
||||||
|
|
||||||
|
if supersample > 1:
|
||||||
|
flattened = flattened.resize(
|
||||||
|
(
|
||||||
|
max(1, round(flattened.width / supersample)),
|
||||||
|
max(1, round(flattened.height / supersample)),
|
||||||
|
),
|
||||||
|
Image.Resampling.LANCZOS,
|
||||||
|
)
|
||||||
|
|
||||||
|
flattened.thumbnail((_THUMBNAIL_MAX_WIDTH, _THUMBNAIL_MAX_HEIGHT))
|
||||||
|
flattened.save(out_path, format="WEBP")
|
||||||
|
except (OSError, Image.DecompressionBombError) as e:
|
||||||
|
raise ParseError(f"Unable to encode thumbnail from {png_path}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_thumbnail_dpi(in_path: Path, logging_group=None) -> tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Returns (dpi, supersample). Unknown geometry is not supersampled, since
|
||||||
|
the render size cannot be bounded.
|
||||||
|
"""
|
||||||
|
from paperless.parsers.utils import get_pdf_first_page_size_points
|
||||||
|
|
||||||
|
size = get_pdf_first_page_size_points(in_path)
|
||||||
|
if size is None:
|
||||||
|
logger.debug(
|
||||||
|
"Could not read PDF page size, using fallback DPI",
|
||||||
|
extra={"group": logging_group},
|
||||||
|
)
|
||||||
|
return _THUMBNAIL_FALLBACK_DPI, 1
|
||||||
|
|
||||||
|
width_pts, height_pts = size
|
||||||
|
dpi_for_width = _THUMBNAIL_MAX_WIDTH * 72 / width_pts
|
||||||
|
dpi_for_height = _THUMBNAIL_MAX_HEIGHT * 72 / height_pts
|
||||||
|
# Round up so the downsampled render is never a few pixels short of the
|
||||||
|
# target; the shrink-only clamp in encode_thumbnail_webp trims the excess.
|
||||||
|
dpi = max(
|
||||||
|
1,
|
||||||
|
math.ceil(min(_THUMBNAIL_MAX_DPI, dpi_for_width, dpi_for_height)),
|
||||||
|
)
|
||||||
|
# At the 1 DPI floor the page is already oversized, so do not supersample
|
||||||
|
return dpi, 1 if dpi == 1 else _THUMBNAIL_SUPERSAMPLE
|
||||||
|
|
||||||
|
|
||||||
|
def _render_pdf_thumbnail(
|
||||||
|
in_path: Path,
|
||||||
|
png_path: Path,
|
||||||
|
out_path: Path,
|
||||||
|
logging_group=None,
|
||||||
|
) -> None:
|
||||||
|
dpi, supersample = _compute_thumbnail_dpi(in_path, logging_group=logging_group)
|
||||||
|
rasterize_pdf_page_to_png(
|
||||||
|
in_path,
|
||||||
|
png_path,
|
||||||
|
dpi=dpi * supersample,
|
||||||
|
logging_group=logging_group,
|
||||||
|
)
|
||||||
|
encode_thumbnail_webp(png_path, out_path, supersample=supersample)
|
||||||
|
|
||||||
|
|
||||||
|
def _repair_pdf_with_qpdf(in_path: Path, out_path: Path) -> None:
|
||||||
|
# qpdf exits 3 after a repair; --warning-exit-0 keeps that from failing
|
||||||
|
try:
|
||||||
|
shutil.copy(in_path, out_path)
|
||||||
|
run_subprocess(
|
||||||
|
["qpdf", "--warning-exit-0", "--replace-input", str(out_path)],
|
||||||
|
logger=logger,
|
||||||
|
)
|
||||||
|
except (subprocess.CalledProcessError, OSError) as e:
|
||||||
|
raise ParseError(f"qpdf repair failed for {in_path}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def make_thumbnail_from_pdf_qpdf_fallback(
|
||||||
|
in_path: Path,
|
||||||
|
temp_dir: Path,
|
||||||
|
logging_group=None,
|
||||||
|
) -> Path:
|
||||||
|
png_path = temp_dir / "page1_repaired.png"
|
||||||
|
out_path = temp_dir / "convert_qpdf.webp"
|
||||||
|
repaired_path = temp_dir / "repaired.pdf"
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
"Thumbnail generation with pdftoppm failed, attempting qpdf repair and retry.",
|
||||||
|
extra={"group": logging_group},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
_repair_pdf_with_qpdf(in_path, repaired_path)
|
||||||
|
_render_pdf_thumbnail(repaired_path, png_path, out_path, logging_group)
|
||||||
return out_path
|
return out_path
|
||||||
|
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
logger.error(f"Unable to make thumbnail with Ghostscript: {e}")
|
logger.error(f"Unable to make thumbnail after qpdf repair: {e}")
|
||||||
# The caller might expect a generated thumbnail that can be moved,
|
# The caller might expect a generated thumbnail that can be moved,
|
||||||
# so we need to copy it before it gets moved.
|
# so we need to copy it before it gets moved.
|
||||||
# https://github.com/paperless-ngx/paperless-ngx/issues/3631
|
# https://github.com/paperless-ngx/paperless-ngx/issues/3631
|
||||||
default_thumbnail_path: Path = Path(temp_dir) / "document.webp"
|
default_thumbnail_path = temp_dir / "document.webp"
|
||||||
copy_file_with_basic_stats(get_default_thumbnail(), default_thumbnail_path)
|
copy_file_with_basic_stats(get_default_thumbnail(), default_thumbnail_path)
|
||||||
return default_thumbnail_path
|
return default_thumbnail_path
|
||||||
|
|
||||||
@@ -175,25 +245,18 @@ def make_thumbnail_from_pdf(in_path: Path, temp_dir: Path, logging_group=None) -
|
|||||||
"""
|
"""
|
||||||
The thumbnail of a PDF is just a 500px wide image of the first page.
|
The thumbnail of a PDF is just a 500px wide image of the first page.
|
||||||
"""
|
"""
|
||||||
|
png_path: Path = temp_dir / "page1.png"
|
||||||
out_path: Path = temp_dir / "convert.webp"
|
out_path: Path = temp_dir / "convert.webp"
|
||||||
|
|
||||||
# Run convert to get a decent thumbnail
|
|
||||||
try:
|
try:
|
||||||
run_convert(
|
_render_pdf_thumbnail(in_path, png_path, out_path, logging_group)
|
||||||
density=300,
|
|
||||||
scale="500x5000>",
|
|
||||||
alpha="remove",
|
|
||||||
strip=True,
|
|
||||||
trim=False,
|
|
||||||
auto_orient=True,
|
|
||||||
use_cropbox=True,
|
|
||||||
input_file=f"{in_path}[0]",
|
|
||||||
output_file=str(out_path),
|
|
||||||
logging_group=logging_group,
|
|
||||||
)
|
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
logger.error(f"Unable to make thumbnail with convert: {e}")
|
logger.error(f"Unable to make thumbnail with pdftoppm: {e}")
|
||||||
out_path = make_thumbnail_from_pdf_gs_fallback(in_path, temp_dir, logging_group)
|
out_path = make_thumbnail_from_pdf_qpdf_fallback(
|
||||||
|
in_path,
|
||||||
|
temp_dir,
|
||||||
|
logging_group,
|
||||||
|
)
|
||||||
|
|
||||||
return out_path
|
return out_path
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ from documents.search._query import parse_user_query
|
|||||||
from documents.search._schema import _write_sentinels
|
from documents.search._schema import _write_sentinels
|
||||||
from documents.search._schema import build_schema
|
from documents.search._schema import build_schema
|
||||||
from documents.search._schema import open_or_rebuild_index
|
from documents.search._schema import open_or_rebuild_index
|
||||||
from documents.search._schema import rebuild_in_progress
|
|
||||||
from documents.search._schema import wipe_index
|
from documents.search._schema import wipe_index
|
||||||
from documents.search._tokenizer import ascii_fold
|
from documents.search._tokenizer import ascii_fold
|
||||||
from documents.search._tokenizer import autocomplete_tokens
|
from documents.search._tokenizer import autocomplete_tokens
|
||||||
@@ -1111,44 +1110,39 @@ class TantivyBackend:
|
|||||||
flushing a segment, deferring merge work; they do not avoid it.
|
flushing a segment, deferring merge work; they do not avoid it.
|
||||||
"""
|
"""
|
||||||
wipe_index(self._path)
|
wipe_index(self._path)
|
||||||
# The marker covers the window where the empty index is already stamped
|
new_index = tantivy.Index(build_schema(), path=str(self._path))
|
||||||
# as current but not yet populated, so an interrupted rebuild is retried.
|
_write_sentinels(self._path)
|
||||||
with rebuild_in_progress(self._path):
|
register_tokenizers(new_index, settings.SEARCH_LANGUAGE)
|
||||||
new_index = tantivy.Index(build_schema(), path=str(self._path))
|
|
||||||
_write_sentinels(self._path)
|
|
||||||
register_tokenizers(new_index, settings.SEARCH_LANGUAGE)
|
|
||||||
|
|
||||||
# Point instance at the new index so _build_tantivy_doc uses it
|
# Point instance at the new index so _build_tantivy_doc uses it
|
||||||
old_index, old_schema = self._raw_index, self._raw_schema
|
old_index, old_schema = self._raw_index, self._raw_schema
|
||||||
self._raw_index = new_index
|
self._raw_index = new_index
|
||||||
self._raw_schema = new_index.schema
|
self._raw_schema = new_index.schema
|
||||||
# Stream documents one-by-one (so the progress bar advances per
|
# Stream documents one-by-one (so the progress bar advances per
|
||||||
# document) while fetching viewer permissions one SQL query per
|
# document) while fetching viewer permissions one SQL query per chunk.
|
||||||
# chunk. The stream is Sized, so iter_wrapper can still discover
|
# The stream is Sized, so iter_wrapper can still discover the total.
|
||||||
# the total.
|
documents_stream = _DocumentViewerStream(documents, chunk_size=1000)
|
||||||
documents_stream = _DocumentViewerStream(documents, chunk_size=1000)
|
try:
|
||||||
try:
|
writer = new_index.writer(heap_size=writer_heap_bytes)
|
||||||
writer = new_index.writer(heap_size=writer_heap_bytes)
|
for document, (viewer_ids, viewer_group_ids) in iter_wrapper(
|
||||||
for document, (viewer_ids, viewer_group_ids) in iter_wrapper(
|
documents_stream,
|
||||||
documents_stream,
|
):
|
||||||
):
|
doc = self._build_tantivy_doc(
|
||||||
doc = self._build_tantivy_doc(
|
document,
|
||||||
document,
|
viewer_ids=viewer_ids,
|
||||||
viewer_ids=viewer_ids,
|
viewer_group_ids=viewer_group_ids,
|
||||||
viewer_group_ids=viewer_group_ids,
|
)
|
||||||
)
|
writer.add_document(doc)
|
||||||
writer.add_document(doc)
|
writer.commit()
|
||||||
writer.commit()
|
# Wait for background merge threads to finish so all segments are
|
||||||
# Wait for background merge threads to finish so all segments
|
# fully merged and persisted before the index is considered rebuilt.
|
||||||
# are fully merged and persisted before the index is considered
|
writer.wait_merging_threads()
|
||||||
# rebuilt.
|
new_index.reload()
|
||||||
writer.wait_merging_threads()
|
except BaseException: # pragma: no cover
|
||||||
new_index.reload()
|
# Restore old index on failure so the backend remains usable
|
||||||
except BaseException: # pragma: no cover
|
self._raw_index = old_index
|
||||||
# Restore old index on failure so the backend remains usable
|
self._raw_schema = old_schema
|
||||||
self._raw_index = old_index
|
raise
|
||||||
self._raw_schema = old_schema
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def chunked(iterable, size):
|
def chunked(iterable, size):
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
from contextlib import contextmanager
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
@@ -17,7 +16,6 @@ from whoosh_compat import FieldKind
|
|||||||
from documents.search._fields import PUBLIC_FIELDS
|
from documents.search._fields import PUBLIC_FIELDS
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterator
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logger = logging.getLogger("paperless.search")
|
logger = logging.getLogger("paperless.search")
|
||||||
@@ -30,11 +28,6 @@ logger = logging.getLogger("paperless.search")
|
|||||||
# v3 - barcodes JSON field for stored barcode contents
|
# v3 - barcodes JSON field for stored barcode contents
|
||||||
SCHEMA_VERSION: Final[int] = 3
|
SCHEMA_VERSION: Final[int] = 3
|
||||||
|
|
||||||
# Present in the index directory from the moment a full rebuild starts until it
|
|
||||||
# finishes. If a rebuild is interrupted it is left behind, so the half-built
|
|
||||||
# index is not mistaken for a complete one.
|
|
||||||
REBUILD_MARKER: Final[str] = ".rebuilding"
|
|
||||||
|
|
||||||
|
|
||||||
class FieldDescriptor(NamedTuple):
|
class FieldDescriptor(NamedTuple):
|
||||||
"""One tantivy field, in declaration order.
|
"""One tantivy field, in declaration order.
|
||||||
@@ -262,9 +255,9 @@ def needs_rebuild(index_dir: Path) -> bool:
|
|||||||
"""
|
"""
|
||||||
Check if the search index needs rebuilding.
|
Check if the search index needs rebuilding.
|
||||||
|
|
||||||
True if a previous full rebuild never finished (the rebuild marker is still
|
Reads .index_settings.json to compare the stored schema version, search
|
||||||
present), or if the index's stamped settings no longer match the current
|
language and schema fingerprint against the current configuration. Returns
|
||||||
configuration. See _settings_mismatch().
|
True if the file is missing, unparsable, or any value mismatches.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
index_dir: Path to the search index directory
|
index_dir: Path to the search index directory
|
||||||
@@ -272,40 +265,6 @@ def needs_rebuild(index_dir: Path) -> bool:
|
|||||||
Returns:
|
Returns:
|
||||||
True if the index needs rebuilding, False if it's up to date
|
True if the index needs rebuilding, False if it's up to date
|
||||||
"""
|
"""
|
||||||
if (index_dir / REBUILD_MARKER).exists():
|
|
||||||
logger.warning("Previous search index rebuild did not finish - rebuilding.")
|
|
||||||
return True
|
|
||||||
return _settings_mismatch(index_dir)
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def rebuild_in_progress(index_dir: Path) -> Iterator[None]:
|
|
||||||
"""
|
|
||||||
Flag the index as incomplete for the duration of a full rebuild.
|
|
||||||
|
|
||||||
The marker is cleared only if the block exits cleanly. There is deliberately
|
|
||||||
no try/finally: an exception must leave the marker behind so the next
|
|
||||||
needs_rebuild() check retries the rebuild.
|
|
||||||
"""
|
|
||||||
marker = index_dir / REBUILD_MARKER
|
|
||||||
marker.touch()
|
|
||||||
yield
|
|
||||||
marker.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _settings_mismatch(index_dir: Path) -> bool:
|
|
||||||
"""
|
|
||||||
Check the stamped settings against the current configuration.
|
|
||||||
|
|
||||||
Reads .index_settings.json to compare the stored schema version, search
|
|
||||||
language and schema fingerprint. Returns True if the file is missing,
|
|
||||||
unparsable, or any value mismatches.
|
|
||||||
|
|
||||||
This deliberately ignores the rebuild marker: open_or_rebuild_index() uses it
|
|
||||||
so that a process opening the index while another process is mid-rebuild
|
|
||||||
(or after one died) does not wipe the partial index out from under it.
|
|
||||||
Repopulating is the job of ``document_index reindex``.
|
|
||||||
"""
|
|
||||||
settings_file = index_dir / ".index_settings.json"
|
settings_file = index_dir / ".index_settings.json"
|
||||||
if not settings_file.exists():
|
if not settings_file.exists():
|
||||||
return True
|
return True
|
||||||
@@ -374,7 +333,7 @@ def open_or_rebuild_index(index_dir: Path | None = None) -> tantivy.Index:
|
|||||||
index_dir = cast("Path", settings.INDEX_DIR)
|
index_dir = cast("Path", settings.INDEX_DIR)
|
||||||
if not index_dir.exists():
|
if not index_dir.exists():
|
||||||
return tantivy.Index(build_schema())
|
return tantivy.Index(build_schema())
|
||||||
if _settings_mismatch(index_dir):
|
if needs_rebuild(index_dir):
|
||||||
wipe_index(index_dir)
|
wipe_index(index_dir)
|
||||||
idx = tantivy.Index(build_schema(), path=str(index_dir))
|
idx = tantivy.Index(build_schema(), path=str(index_dir))
|
||||||
_write_sentinels(index_dir)
|
_write_sentinels(index_dir)
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ from documents.search._backend import TantivyBackend
|
|||||||
from documents.search._backend import WriteBatch
|
from documents.search._backend import WriteBatch
|
||||||
from documents.search._backend import get_backend
|
from documents.search._backend import get_backend
|
||||||
from documents.search._backend import reset_backend
|
from documents.search._backend import reset_backend
|
||||||
from documents.search._schema import REBUILD_MARKER
|
|
||||||
from documents.search._schema import needs_rebuild
|
|
||||||
from documents.signals.handlers import add_to_index
|
from documents.signals.handlers import add_to_index
|
||||||
from paperless_testing.dirs import PaperlessDirs
|
|
||||||
from paperless_testing.factories import CorrespondentFactory
|
from paperless_testing.factories import CorrespondentFactory
|
||||||
from paperless_testing.factories import DocumentFactory
|
from paperless_testing.factories import DocumentFactory
|
||||||
from paperless_testing.factories import DocumentTypeFactory
|
from paperless_testing.factories import DocumentTypeFactory
|
||||||
@@ -826,53 +823,6 @@ class TestRebuild:
|
|||||||
backend.rebuild(Document.objects.all(), iter_wrapper=wrapper)
|
backend.rebuild(Document.objects.all(), iter_wrapper=wrapper)
|
||||||
assert 30 in seen
|
assert 30 in seen
|
||||||
|
|
||||||
def test_successful_rebuild_leaves_index_up_to_date(
|
|
||||||
self,
|
|
||||||
backend: TantivyBackend,
|
|
||||||
paperless_dirs: PaperlessDirs,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A backend and one document
|
|
||||||
WHEN:
|
|
||||||
- rebuild() completes
|
|
||||||
THEN:
|
|
||||||
- needs_rebuild() is False and no rebuild marker remains
|
|
||||||
"""
|
|
||||||
DocumentFactory.create()
|
|
||||||
|
|
||||||
backend.rebuild(Document.objects.all())
|
|
||||||
|
|
||||||
assert needs_rebuild(paperless_dirs.index_dir) is False
|
|
||||||
assert not (paperless_dirs.index_dir / REBUILD_MARKER).exists()
|
|
||||||
|
|
||||||
def test_interrupted_rebuild_is_retried(
|
|
||||||
self,
|
|
||||||
backend: TantivyBackend,
|
|
||||||
paperless_dirs: PaperlessDirs,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A rebuild that dies while indexing documents (e.g. the database
|
|
||||||
connection is lost)
|
|
||||||
WHEN:
|
|
||||||
- needs_rebuild() is checked afterwards
|
|
||||||
THEN:
|
|
||||||
- It is True, even though the empty index was already stamped with
|
|
||||||
current settings, so the next start rebuilds instead of reporting
|
|
||||||
the index as up to date
|
|
||||||
"""
|
|
||||||
DocumentFactory.create()
|
|
||||||
|
|
||||||
def die(pairs):
|
|
||||||
raise RuntimeError("terminating connection due to administrator command")
|
|
||||||
yield # pragma: no cover
|
|
||||||
|
|
||||||
with pytest.raises(RuntimeError):
|
|
||||||
backend.rebuild(Document.objects.all(), iter_wrapper=die)
|
|
||||||
|
|
||||||
assert needs_rebuild(paperless_dirs.index_dir) is True
|
|
||||||
|
|
||||||
def test_includes_group_granted_viewers(self, backend: TantivyBackend) -> None:
|
def test_includes_group_granted_viewers(self, backend: TantivyBackend) -> None:
|
||||||
"""Rebuild must index viewer ids for group-only grants, not just direct ones.
|
"""Rebuild must index viewer ids for group-only grants, not just direct ones.
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
|
import subprocess
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pikepdf
|
||||||
import pytest
|
import pytest
|
||||||
|
from PIL import Image
|
||||||
from pytest_django.fixtures import Settings
|
from pytest_django.fixtures import Settings
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from documents.parsers import ParseError
|
||||||
|
from documents.parsers import _compute_thumbnail_dpi
|
||||||
|
from documents.parsers import encode_thumbnail_webp
|
||||||
from documents.parsers import get_default_file_extension
|
from documents.parsers import get_default_file_extension
|
||||||
|
from documents.parsers import get_default_thumbnail
|
||||||
from documents.parsers import get_supported_file_extensions
|
from documents.parsers import get_supported_file_extensions
|
||||||
from documents.parsers import is_file_ext_supported
|
from documents.parsers import is_file_ext_supported
|
||||||
|
from documents.parsers import make_thumbnail_from_pdf
|
||||||
|
from documents.parsers import rasterize_pdf_page_to_png
|
||||||
from paperless.parsers.registry import get_parser_registry
|
from paperless.parsers.registry import get_parser_registry
|
||||||
from paperless.parsers.registry import reset_parser_registry
|
from paperless.parsers.registry import reset_parser_registry
|
||||||
from paperless.parsers.tesseract import RasterisedDocumentParser
|
from paperless.parsers.tesseract import RasterisedDocumentParser
|
||||||
@@ -125,3 +136,374 @@ class TestParserAvailability:
|
|||||||
assert is_file_ext_supported(".pdf")
|
assert is_file_ext_supported(".pdf")
|
||||||
assert not is_file_ext_supported(".hsdfh")
|
assert not is_file_ext_supported(".hsdfh")
|
||||||
assert not is_file_ext_supported("")
|
assert not is_file_ext_supported("")
|
||||||
|
|
||||||
|
|
||||||
|
class TestComputeThumbnailDpi:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("size", "expected"),
|
||||||
|
[
|
||||||
|
pytest.param((612.0, 792.0), (59, 2), id="letter-width-bound"),
|
||||||
|
pytest.param((792.0, 612.0), (46, 2), id="landscape-rounded-up"),
|
||||||
|
pytest.param((612.0, 100000.0), (4, 2), id="tall-strip-height-bound"),
|
||||||
|
pytest.param((200.0, 300.0), (180, 2), id="small-page-width-bound"),
|
||||||
|
pytest.param((72.0, 72.0), (300, 2), id="tiny-page-capped-at-300"),
|
||||||
|
pytest.param(
|
||||||
|
(1000000.0, 1000000.0),
|
||||||
|
(1, 1),
|
||||||
|
id="huge-page-minimum-one-unsupersampled",
|
||||||
|
),
|
||||||
|
pytest.param(None, (150, 1), id="unreadable-geometry-fallback"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_dpi_from_page_size(
|
||||||
|
self,
|
||||||
|
mocker: MockerFixture,
|
||||||
|
tmp_path: Path,
|
||||||
|
size: tuple[float, float] | None,
|
||||||
|
expected: tuple[int, int],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A first page of the given size, or unreadable geometry
|
||||||
|
WHEN:
|
||||||
|
- The thumbnail DPI is computed
|
||||||
|
THEN:
|
||||||
|
- The expected DPI and supersample factor are returned
|
||||||
|
"""
|
||||||
|
mocker.patch(
|
||||||
|
"paperless.parsers.utils.get_pdf_first_page_size_points",
|
||||||
|
return_value=size,
|
||||||
|
)
|
||||||
|
assert _compute_thumbnail_dpi(tmp_path / "doc.pdf") == expected
|
||||||
|
|
||||||
|
|
||||||
|
class TestMakeThumbnailFromPdf:
|
||||||
|
@pytest.fixture
|
||||||
|
def work_dir(self, tmp_path: Path) -> Path:
|
||||||
|
path = tmp_path / "work"
|
||||||
|
path.mkdir()
|
||||||
|
return path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _write_blank_pdf(path: Path, page_size: tuple[int, int] = (612, 792)) -> Path:
|
||||||
|
pdf = pikepdf.new()
|
||||||
|
pdf.add_blank_page(page_size=page_size)
|
||||||
|
pdf.save(path, object_stream_mode=pikepdf.ObjectStreamMode.disable)
|
||||||
|
return path
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("size", "expected_dpi", "expected_supersample"),
|
||||||
|
[
|
||||||
|
pytest.param((612.0, 792.0), 118, 2, id="known-geometry-2x"),
|
||||||
|
pytest.param(None, 150, 1, id="unreadable-geometry-plain-fallback"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_render_dpi_requested(
|
||||||
|
self,
|
||||||
|
mocker: MockerFixture,
|
||||||
|
tmp_path: Path,
|
||||||
|
work_dir: Path,
|
||||||
|
size: tuple[float, float] | None,
|
||||||
|
expected_dpi: int,
|
||||||
|
expected_supersample: int,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Readable or unreadable page geometry
|
||||||
|
WHEN:
|
||||||
|
- A thumbnail is made
|
||||||
|
THEN:
|
||||||
|
- Rasterize and encode get the matching DPI and supersample factor
|
||||||
|
"""
|
||||||
|
mocker.patch(
|
||||||
|
"paperless.parsers.utils.get_pdf_first_page_size_points",
|
||||||
|
return_value=size,
|
||||||
|
)
|
||||||
|
rasterize = mocker.patch("documents.parsers.rasterize_pdf_page_to_png")
|
||||||
|
encode = mocker.patch("documents.parsers.encode_thumbnail_webp")
|
||||||
|
|
||||||
|
make_thumbnail_from_pdf(tmp_path / "in.pdf", work_dir)
|
||||||
|
|
||||||
|
assert rasterize.call_args.kwargs["dpi"] == expected_dpi
|
||||||
|
assert encode.call_args.kwargs["supersample"] == expected_supersample
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("page_size", "expected_width"),
|
||||||
|
[
|
||||||
|
pytest.param((612, 792), 500, id="letter"),
|
||||||
|
pytest.param((792, 612), 500, id="landscape-letter"),
|
||||||
|
pytest.param((595, 842), 500, id="a4"),
|
||||||
|
pytest.param((200, 300), 500, id="small-page"),
|
||||||
|
pytest.param((72, 72), 300, id="tiny-page-capped"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_thumbnail_width(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
work_dir: Path,
|
||||||
|
page_size: tuple[int, int],
|
||||||
|
expected_width: int,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A PDF whose first page has the given size in points
|
||||||
|
WHEN:
|
||||||
|
- A thumbnail is made from it
|
||||||
|
THEN:
|
||||||
|
- The thumbnail has the expected width
|
||||||
|
"""
|
||||||
|
pdf_path = self._write_blank_pdf(tmp_path / "in.pdf", page_size)
|
||||||
|
|
||||||
|
thumb = make_thumbnail_from_pdf(pdf_path, work_dir)
|
||||||
|
|
||||||
|
assert thumb == work_dir / "convert.webp"
|
||||||
|
with Image.open(thumb) as im:
|
||||||
|
assert im.format == "WEBP"
|
||||||
|
assert im.width == expected_width
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _write_pdf_without_xref(cls, path: Path) -> Path:
|
||||||
|
"""
|
||||||
|
Cuts off the xref and trailer, which pdftoppm cannot recover from but qpdf can.
|
||||||
|
"""
|
||||||
|
cls._write_blank_pdf(path)
|
||||||
|
data = path.read_bytes()
|
||||||
|
path.write_bytes(data[: data.rindex(b"\nxref")])
|
||||||
|
return path
|
||||||
|
|
||||||
|
def test_qpdf_repair_produces_real_thumbnail(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
work_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A PDF with its xref table and trailer cut off
|
||||||
|
WHEN:
|
||||||
|
- A thumbnail is made from it
|
||||||
|
THEN:
|
||||||
|
- The thumbnail is rendered from a qpdf repaired copy
|
||||||
|
- The original file is unchanged
|
||||||
|
"""
|
||||||
|
pdf_path = self._write_pdf_without_xref(tmp_path / "broken.pdf")
|
||||||
|
original_bytes = pdf_path.read_bytes()
|
||||||
|
|
||||||
|
with pytest.raises(ParseError):
|
||||||
|
rasterize_pdf_page_to_png(pdf_path, work_dir / "probe.png", dpi=50)
|
||||||
|
|
||||||
|
thumb = make_thumbnail_from_pdf(pdf_path, work_dir)
|
||||||
|
|
||||||
|
assert thumb == work_dir / "convert_qpdf.webp"
|
||||||
|
with Image.open(thumb) as im:
|
||||||
|
assert im.format == "WEBP"
|
||||||
|
assert im.width == 500
|
||||||
|
assert pdf_path.read_bytes() == original_bytes
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"qpdf_error",
|
||||||
|
[
|
||||||
|
pytest.param(subprocess.CalledProcessError(2, "qpdf"), id="qpdf-fails"),
|
||||||
|
pytest.param(None, id="repaired-still-unrenderable"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_double_failure_uses_default_thumbnail(
|
||||||
|
self,
|
||||||
|
mocker: MockerFixture,
|
||||||
|
tmp_path: Path,
|
||||||
|
work_dir: Path,
|
||||||
|
qpdf_error: subprocess.CalledProcessError | None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A PDF that cannot be rendered, even after qpdf repair
|
||||||
|
WHEN:
|
||||||
|
- A thumbnail is made from it
|
||||||
|
THEN:
|
||||||
|
- A copy of the default thumbnail is returned
|
||||||
|
"""
|
||||||
|
mocker.patch(
|
||||||
|
"documents.parsers.rasterize_pdf_page_to_png",
|
||||||
|
side_effect=ParseError("Does not compute."),
|
||||||
|
)
|
||||||
|
if qpdf_error is not None:
|
||||||
|
mocker.patch("documents.parsers.run_subprocess", side_effect=qpdf_error)
|
||||||
|
pdf_path = self._write_blank_pdf(tmp_path / "in.pdf")
|
||||||
|
|
||||||
|
thumb = make_thumbnail_from_pdf(pdf_path, work_dir)
|
||||||
|
|
||||||
|
assert thumb == work_dir / "document.webp"
|
||||||
|
assert thumb.read_bytes() == get_default_thumbnail().read_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
class TestRasterizePdfPageToPng:
|
||||||
|
@staticmethod
|
||||||
|
def _write_pdf(
|
||||||
|
path: Path,
|
||||||
|
*,
|
||||||
|
crop_box: tuple[float, float, float, float] | None = None,
|
||||||
|
rotate: int | None = None,
|
||||||
|
) -> Path:
|
||||||
|
pdf = pikepdf.new()
|
||||||
|
pdf.add_blank_page(page_size=(144, 72))
|
||||||
|
pdf.add_blank_page(page_size=(300, 300))
|
||||||
|
page = pdf.pages[0]
|
||||||
|
if crop_box is not None:
|
||||||
|
page.obj.CropBox = pikepdf.Array(crop_box)
|
||||||
|
if rotate is not None:
|
||||||
|
page.obj.Rotate = rotate
|
||||||
|
pdf.save(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("crop_box", "rotate", "expected_size"),
|
||||||
|
[
|
||||||
|
pytest.param(None, None, (144, 72), id="plain"),
|
||||||
|
pytest.param(None, 90, (72, 144), id="rotated-90"),
|
||||||
|
pytest.param((0, 0, 72, 36), None, (72, 36), id="crop-box"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_renders_first_page_to_exact_path(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
crop_box: tuple[float, float, float, float] | None,
|
||||||
|
rotate: int | None,
|
||||||
|
expected_size: tuple[int, int],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A two page PDF, first page optionally cropped or rotated
|
||||||
|
WHEN:
|
||||||
|
- The first page is rasterized at 72 DPI
|
||||||
|
THEN:
|
||||||
|
- Only out_path is written, sized to the first page's crop and rotation
|
||||||
|
"""
|
||||||
|
pdf_path = self._write_pdf(
|
||||||
|
tmp_path / "in.pdf",
|
||||||
|
crop_box=crop_box,
|
||||||
|
rotate=rotate,
|
||||||
|
)
|
||||||
|
out_dir = tmp_path / "out"
|
||||||
|
out_dir.mkdir()
|
||||||
|
out_path = out_dir / "page1.png"
|
||||||
|
|
||||||
|
rasterize_pdf_page_to_png(pdf_path, out_path, dpi=72)
|
||||||
|
|
||||||
|
assert list(out_dir.iterdir()) == [out_path]
|
||||||
|
with Image.open(out_path) as im:
|
||||||
|
assert im.format == "PNG"
|
||||||
|
assert im.size == expected_size
|
||||||
|
|
||||||
|
def test_failure_raises_parse_error(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A file that is not a PDF
|
||||||
|
WHEN:
|
||||||
|
- Rasterization is attempted
|
||||||
|
THEN:
|
||||||
|
- A ParseError is raised
|
||||||
|
"""
|
||||||
|
bad = tmp_path / "bad.pdf"
|
||||||
|
bad.write_bytes(b"not a pdf")
|
||||||
|
|
||||||
|
with pytest.raises(ParseError):
|
||||||
|
rasterize_pdf_page_to_png(bad, tmp_path / "page1.png", dpi=72)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEncodeThumbnailWebp:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("mode", "color"),
|
||||||
|
[
|
||||||
|
pytest.param("RGBA", (0, 0, 0, 0), id="rgba"),
|
||||||
|
pytest.param("LA", (0, 0), id="la"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_alpha_flattened_onto_white(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
mode: str,
|
||||||
|
color: tuple[int, ...],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A fully transparent PNG with an alpha channel
|
||||||
|
WHEN:
|
||||||
|
- It is encoded as a thumbnail
|
||||||
|
THEN:
|
||||||
|
- The WebP output is RGB with the transparency flattened to white
|
||||||
|
"""
|
||||||
|
png_path = tmp_path / "in.png"
|
||||||
|
Image.new(mode, (20, 10), color).save(png_path)
|
||||||
|
out_path = tmp_path / "out.webp"
|
||||||
|
|
||||||
|
encode_thumbnail_webp(png_path, out_path)
|
||||||
|
|
||||||
|
with Image.open(out_path) as im:
|
||||||
|
assert im.format == "WEBP"
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
assert im.size == (20, 10)
|
||||||
|
red, green, blue = im.getpixel((10, 5))
|
||||||
|
assert min(red, green, blue) >= 250
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("in_size", "supersample", "expected_size"),
|
||||||
|
[
|
||||||
|
pytest.param((1000, 2000), 1, (500, 1000), id="too-wide-shrunk"),
|
||||||
|
pytest.param((100, 10000), 1, (50, 5000), id="too-tall-shrunk"),
|
||||||
|
pytest.param((100, 200), 1, (100, 200), id="small-not-enlarged"),
|
||||||
|
pytest.param((1000, 1400), 2, (500, 700), id="2x-halved"),
|
||||||
|
pytest.param((1001, 1401), 2, (500, 700), id="2x-odd-rounded"),
|
||||||
|
pytest.param((600, 800), 2, (300, 400), id="2x-small-not-enlarged"),
|
||||||
|
pytest.param((900, 1200), 2, (450, 600), id="2x-below-clamp"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_size_clamped_and_downsampled(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
in_size: tuple[int, int],
|
||||||
|
supersample: int,
|
||||||
|
expected_size: tuple[int, int],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A rendered image and its supersample factor
|
||||||
|
WHEN:
|
||||||
|
- It is encoded as a thumbnail with that factor
|
||||||
|
THEN:
|
||||||
|
- It is downsampled, fit within 500x5000 and never enlarged
|
||||||
|
"""
|
||||||
|
png_path = tmp_path / "in.png"
|
||||||
|
Image.new("RGB", in_size, (255, 255, 255)).save(png_path)
|
||||||
|
out_path = tmp_path / "out.webp"
|
||||||
|
|
||||||
|
encode_thumbnail_webp(png_path, out_path, supersample=supersample)
|
||||||
|
|
||||||
|
with Image.open(out_path) as im:
|
||||||
|
assert im.size == expected_size
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"error",
|
||||||
|
[
|
||||||
|
pytest.param(OSError("broken image"), id="os-error"),
|
||||||
|
pytest.param(Image.DecompressionBombError("too large"), id="bomb"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_decode_failure_raises_parse_error(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
mocker: MockerFixture,
|
||||||
|
error: Exception,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Opening the rendered image fails
|
||||||
|
WHEN:
|
||||||
|
- It is encoded as a thumbnail
|
||||||
|
THEN:
|
||||||
|
- A ParseError is raised
|
||||||
|
"""
|
||||||
|
png_path = tmp_path / "in.png"
|
||||||
|
Image.new("RGB", (10, 10)).save(png_path)
|
||||||
|
mocker.patch("PIL.Image.open", side_effect=error)
|
||||||
|
|
||||||
|
with pytest.raises(ParseError):
|
||||||
|
encode_thumbnail_webp(png_path, tmp_path / "out.webp")
|
||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Afrikaans\n"
|
"Language-Team: Afrikaans\n"
|
||||||
"Language: af_ZA\n"
|
"Language: af_ZA\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Amharic\n"
|
"Language-Team: Amharic\n"
|
||||||
"Language: am_ET\n"
|
"Language: am_ET\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Arabic\n"
|
"Language-Team: Arabic\n"
|
||||||
"Language: ar_SA\n"
|
"Language: ar_SA\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr "أنت على وشك استعمال حسابك في %(provider_name)s ل
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "كخطوة أخيرة، يرجى ملء النموذج التالي:"
|
msgstr "كخطوة أخيرة، يرجى ملء النموذج التالي:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Belarusian\n"
|
"Language-Team: Belarusian\n"
|
||||||
"Language: be_BY\n"
|
"Language: be_BY\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Bulgarian\n"
|
"Language-Team: Bulgarian\n"
|
||||||
"Language: bg_BG\n"
|
"Language: bg_BG\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr "На път сте да използвате профила си в %(pr
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Като последна стъпка, моля, попълнете следната форма:"
|
msgstr "Като последна стъпка, моля, попълнете следната форма:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Catalan\n"
|
"Language-Team: Catalan\n"
|
||||||
"Language: ca_ES\n"
|
"Language: ca_ES\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Estàs a punt d'emprar el teu %(provider_name)s compte per a fer login."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Com a pas final, completa el següent formulari:"
|
msgstr "Com a pas final, completa el següent formulari:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "No es pot analitzar l'URI {value}, falta l'esquema"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "No es pot analitzar l'URI {value}, falta la ubicació de xarxa o la ruta"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "L'esquema d'URI '{parts.scheme}' no està permès. Esquemes permesos: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "No s'ha pogut analitzar l'URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Czech\n"
|
"Language-Team: Czech\n"
|
||||||
"Language: cs_CZ\n"
|
"Language: cs_CZ\n"
|
||||||
@@ -1913,9 +1913,25 @@ msgstr "Chystáte se použít svůj účet %(provider_name)s k přihlášení."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Jako poslední krok vyplňte následující formulář:"
|
msgstr "Jako poslední krok vyplňte následující formulář:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Nelze analyzovat URI {value}, chybí schéma"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Nelze analyzovat URI {value}, chybí síťové umístění nebo cesta"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI schéma '{parts.scheme}' není povoleno. Povolená schémata: {',\n"
|
||||||
|
"'.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Nelze zpracovat URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Danish\n"
|
"Language-Team: Danish\n"
|
||||||
"Language: da_DK\n"
|
"Language: da_DK\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr "En %(provider_name)s-konto er ved at blive brugt til indlogning."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Som sidste trin, udfyld venligst flg. formular:"
|
msgstr "Som sidste trin, udfyld venligst flg. formular:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German, Switzerland\n"
|
"Language-Team: German, Switzerland\n"
|
||||||
"Language: de_CH\n"
|
"Language: de_CH\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Sie sind dabei, Ihr %(provider_name)s-Konto zur Anmeldung zu verwenden."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Bitte füllen Sie zum Abschluss folgendes Formular aus:"
|
msgstr "Bitte füllen Sie zum Abschluss folgendes Formular aus:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "URI {value} kann nicht gelesen werden, fehlendes Schema"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "URI {value} kann nicht gelesen werden, Netzadresse oder Pfad fehlt"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI-Schema „{parts.scheme}“ ist nicht erlaubt. Erlaubte Schemata: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "URI {value} kann nicht gelesen werden"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-07 00:59\n"
|
"PO-Revision-Date: 2026-10-06 00:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
"Language: de_DE\n"
|
"Language: de_DE\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Sie sind dabei, Ihr %(provider_name)s-Konto zur Anmeldung zu verwenden."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Bitte füllen Sie zum Abschluss folgendes Formular aus:"
|
msgstr "Bitte füllen Sie zum Abschluss folgendes Formular aus:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "URI {value} kann nicht gelesen werden, fehlendes Schema"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "URI {value} kann nicht gelesen werden, Netzadresse oder Pfad fehlt"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI-Schema „{parts.scheme}“ ist nicht erlaubt. Erlaubte Schemata: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "URI {value} kann nicht gelesen werden"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Greek\n"
|
"Language-Team: Greek\n"
|
||||||
"Language: el_GR\n"
|
"Language: el_GR\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr "Πρόκειται να χρησιμοποιήσετε το λογαρι
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Ως τελικό βήμα, παρακαλούμε να συμπληρώσετε την ακόλουθη φόρμα:"
|
msgstr "Ως τελικό βήμα, παρακαλούμε να συμπληρώσετε την ακόλουθη φόρμα:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Spanish\n"
|
"Language-Team: Spanish\n"
|
||||||
"Language: es_ES\n"
|
"Language: es_ES\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Estás a punto de usar tu cuenta de %(provider_name)s para iniciar sesi
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Como último paso, por favor complete el siguiente formulario:"
|
msgstr "Como último paso, por favor complete el siguiente formulario:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "No se puede analizar la URI {value}, falta el esquema"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "No se puede analizar la URI {value}, falta la ubicación de la red o la ruta"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "El esquema URI '{parts.scheme}' no está permitido. Esquemas permitidos: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "No se puede analizar la URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Estonian\n"
|
"Language-Team: Estonian\n"
|
||||||
"Language: et_EE\n"
|
"Language: et_EE\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Persian\n"
|
"Language-Team: Persian\n"
|
||||||
"Language: fa_IR\n"
|
"Language: fa_IR\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "به عنوان مرحله آخر ، لطفاً فرم زیر را تکمیل کنید:"
|
msgstr "به عنوان مرحله آخر ، لطفاً فرم زیر را تکمیل کنید:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Finnish\n"
|
"Language-Team: Finnish\n"
|
||||||
"Language: fi_FI\n"
|
"Language: fi_FI\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: French\n"
|
"Language-Team: French\n"
|
||||||
"Language: fr_FR\n"
|
"Language: fr_FR\n"
|
||||||
@@ -1912,9 +1912,24 @@ msgstr "Vous vous apprêtez à utiliser votre compte %(provider_name)s pour vous
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Pour finir, veuillez remplir le formulaire suivant :"
|
msgstr "Pour finir, veuillez remplir le formulaire suivant :"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Impossible d'analyser l'URI {value}, schéma manquant"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Impossible d'analyser l'URI {value}, emplacement réseau ou chemin manquant"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Le schéma d'URI « {parts.scheme} » n'est pas autorisé. Schémas autorisés : {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Impossible d'analyser l'URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hebrew\n"
|
"Language-Team: Hebrew\n"
|
||||||
"Language: he_IL\n"
|
"Language: he_IL\n"
|
||||||
@@ -1914,9 +1914,24 @@ msgstr "שלך כדי להתחבר %(provider_name)s אתה עומד להשתמ
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ": כצעד אחרון, אנא מלא את הטופס הבא:"
|
msgstr ": כצעד אחרון, אנא מלא את הטופס הבא:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "לא ניתן לפענח את ה-URI {value}, חסר פרוטוקול (scheme)"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "לא ניתן לפענח את ה-URI {value}, חסר מיקום רשת (net location) או נתיב"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "פרוטוקול ה-URI '{parts.scheme}' אינו מורשה. הפרוטוקולים המורשים הם: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "לא ניתן לפענח את ה URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hindi\n"
|
"Language-Team: Hindi\n"
|
||||||
"Language: hi_IN\n"
|
"Language: hi_IN\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Croatian\n"
|
"Language-Team: Croatian\n"
|
||||||
"Language: hr_HR\n"
|
"Language: hr_HR\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Upravo ćete koristiti svoj račun %(provider_name)s za prijavu."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Kao posljednji korak ispunite sljedeći obrazac:"
|
msgstr "Kao posljednji korak ispunite sljedeći obrazac:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Nije moguće analizirati URI {value}, nedostaje shema"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Nije moguće analizirati URI {value}, nedostaje mrežna lokacija ili put"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI shema '{parts.scheme}' nije dopuštena. Dopuštene sheme: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Nije moguće raščlaniti URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hungarian\n"
|
"Language-Team: Hungarian\n"
|
||||||
"Language: hu_HU\n"
|
"Language: hu_HU\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "A %(provider_name)s felhasználói fiókkal fogsz belépni."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Utolsó lépésként kérjük válaszoljon az alábbi kérdésekre:"
|
msgstr "Utolsó lépésként kérjük válaszoljon az alábbi kérdésekre:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "A {value} URI értelmezése sikertelen, hiányzó séma"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "A {value} URI értelmezése sikertelen, hiányzó hálózati vagy elérési útvonal"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "A '{parts.scheme}' séma nem engedélyezett. Engedélyezett sémák: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "A {value} URI értelmezése sikertelen"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Indonesian\n"
|
"Language-Team: Indonesian\n"
|
||||||
"Language: id_ID\n"
|
"Language: id_ID\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Anda akan menggunakan akun %(provider_name)s Anda untuk masuk."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Sebagai langkah terakhir, mohon formulir berikut:"
|
msgstr "Sebagai langkah terakhir, mohon formulir berikut:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Gagal membaca URI {value}, skema tidak ditemukan"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Gagal membaca URI {value}, lokasi jaringan atau path tidak ditemukan"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Skema URI '{parts.scheme}' tidak diizinkan. Skema yang diizinkan: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Gagal membaca URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-08 00:58\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Italian\n"
|
"Language-Team: Italian\n"
|
||||||
"Language: it_IT\n"
|
"Language: it_IT\n"
|
||||||
@@ -253,7 +253,7 @@ msgstr "documento radice per questa versione"
|
|||||||
|
|
||||||
#: documents/models.py:325
|
#: documents/models.py:325
|
||||||
msgid "version index"
|
msgid "version index"
|
||||||
msgstr "indice versione"
|
msgstr "indice della versione"
|
||||||
|
|
||||||
#: documents/models.py:329
|
#: documents/models.py:329
|
||||||
msgid "Index of this version within the root document."
|
msgid "Index of this version within the root document."
|
||||||
@@ -261,11 +261,11 @@ msgstr "Indice di questa versione all'interno del documento radice."
|
|||||||
|
|
||||||
#: documents/models.py:333
|
#: documents/models.py:333
|
||||||
msgid "version label"
|
msgid "version label"
|
||||||
msgstr "etichetta versione"
|
msgstr "etichetta della versione"
|
||||||
|
|
||||||
#: documents/models.py:337
|
#: documents/models.py:337
|
||||||
msgid "Optional short label for a document version."
|
msgid "Optional short label for a document version."
|
||||||
msgstr "Etichetta abbreviata facoltativa per una versione del documento."
|
msgstr "Etichetta breve facoltativa per una versione del documento."
|
||||||
|
|
||||||
#: documents/models.py:342 documents/models.py:961 documents/models.py:992
|
#: documents/models.py:342 documents/models.py:961 documents/models.py:992
|
||||||
#: documents/models.py:1096 documents/models.py:2126
|
#: documents/models.py:1096 documents/models.py:2126
|
||||||
@@ -1006,23 +1006,23 @@ msgstr "note"
|
|||||||
|
|
||||||
#: documents/models.py:996
|
#: documents/models.py:996
|
||||||
msgid "page"
|
msgid "page"
|
||||||
msgstr "pagina"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:997
|
#: documents/models.py:997
|
||||||
msgid "Page of the original file, starting at 1"
|
msgid "Page of the original file, starting at 1"
|
||||||
msgstr "Pagina del file originale, partendo da 1"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1052
|
#: documents/models.py:1052
|
||||||
msgid "format"
|
msgid "format"
|
||||||
msgstr "formato"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1056
|
#: documents/models.py:1056
|
||||||
msgid "document barcode"
|
msgid "document barcode"
|
||||||
msgstr "codice a barre del documento"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1057
|
#: documents/models.py:1057
|
||||||
msgid "document barcodes"
|
msgid "document barcodes"
|
||||||
msgstr "codici a barre del documento"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1066
|
#: documents/models.py:1066
|
||||||
msgid "Original"
|
msgid "Original"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Stai per utilizzare il tuo account %(provider_name)s per effettuare il l
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Come ultimo passo, compila il seguente modulo:"
|
msgstr "Come ultimo passo, compila il seguente modulo:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Impossibile analizzare l'URI {value}, schema mancante"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Impossibile analizzare l'URI {value}, posizione o percorso di rete mancante"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ", "
|
msgstr "Lo schema URI '{parts.scheme}' non è consentito. Schemi consentiti: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Impossibile analizzare l'URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
@@ -2168,7 +2183,7 @@ msgstr "Abilita la suddivisione sui codici a barre dei tag"
|
|||||||
|
|
||||||
#: paperless/models.py:308
|
#: paperless/models.py:308
|
||||||
msgid "Stores the values of detected barcodes"
|
msgid "Stores the values of detected barcodes"
|
||||||
msgstr "Memorizza i valori dei codici a barre individuati"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:318
|
#: paperless/models.py:318
|
||||||
msgid "Sets the remote OCR engine"
|
msgid "Sets the remote OCR engine"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Japanese\n"
|
"Language-Team: Japanese\n"
|
||||||
"Language: ja_JP\n"
|
"Language: ja_JP\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr "%(provider_name)s アカウントでログインしようとしていま
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "最後のステップとして、以下のフォームに記入してください"
|
msgstr "最後のステップとして、以下のフォームに記入してください"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Korean\n"
|
"Language-Team: Korean\n"
|
||||||
"Language: ko_KR\n"
|
"Language: ko_KR\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "%(provider_name)s 의 계정을 사용하여 로그인하려고 합니
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "마지막 단계로 다음 양식을 작성해 주세요:"
|
msgstr "마지막 단계로 다음 양식을 작성해 주세요:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "{value} URI를 파싱할 수 없습니다: 스킴이 누락되었습니다."
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "{value} URI를 파싱할 수 없습니다: 네트워크 위치 또는 경로가 누락되었습니다."
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI 스킴 '{parts.scheme}'는 허용되지 않습니다. 허용된 스킴: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "{value} URI를 파싱할 수 없음"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Luxembourgish\n"
|
"Language-Team: Luxembourgish\n"
|
||||||
"Language: lb_LU\n"
|
"Language: lb_LU\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Lithuanian\n"
|
"Language-Team: Lithuanian\n"
|
||||||
"Language: lt_LT\n"
|
"Language: lt_LT\n"
|
||||||
@@ -1912,9 +1912,24 @@ msgstr "Ketinate prisijungimui naudoti savo %(provider_name)s paskyrą."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Galiausiai užpildykite šią formą:"
|
msgstr "Galiausiai užpildykite šią formą:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Nepavyko apdoroti URI {value}, trūksta schemos"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Nepavyko apdoroti URI {value}, trūksta tinklo vietos arba kelio"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI schema '{parts.scheme}' nėra leistina. Galimos schemos: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Nepavyko apdoroti URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Latvian\n"
|
"Language-Team: Latvian\n"
|
||||||
"Language: lv_LV\n"
|
"Language: lv_LV\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Macedonian\n"
|
"Language-Team: Macedonian\n"
|
||||||
"Language: mk_MK\n"
|
"Language: mk_MK\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Malay\n"
|
"Language-Team: Malay\n"
|
||||||
"Language: ms_MY\n"
|
"Language: ms_MY\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Dutch\n"
|
"Language-Team: Dutch\n"
|
||||||
"Language: nl_NL\n"
|
"Language: nl_NL\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr "Je staat op het punt om met je %(provider_name)s account in te loggen."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Vul als laatste stap het volgende formulier in:"
|
msgstr "Vul als laatste stap het volgende formulier in:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Norwegian\n"
|
"Language-Team: Norwegian\n"
|
||||||
"Language: no_NO\n"
|
"Language: no_NO\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "You are about to use your %(provider_name)s account to login. (NO)"
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "As a final step, please complete the following form: (NO)"
|
msgstr "As a final step, please complete the following form: (NO)"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Unable to parse URI {value}, missing scheme (NO)"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Unable to parse URI {value}, missing net location or path (NO)"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)} (NO)"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Unable to parse URI {value} (NO)"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Polish\n"
|
"Language-Team: Polish\n"
|
||||||
"Language: pl_PL\n"
|
"Language: pl_PL\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Zamierzasz użyć konta %(provider_name)s aby się zalogować."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Na koniec należy wypełnić poniższy formularz:"
|
msgstr "Na koniec należy wypełnić poniższy formularz:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Nie można przeanalizować URI {value}, brak schematu"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Nie można przeanalizować URI {value}, brak lokalizacji sieciowej lub ścieżki"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Schemat URI '{parts.scheme}' jest niedozwolony. Dozwolone schematy: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Nie można przetworzyć URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Portuguese, Brazilian\n"
|
"Language-Team: Portuguese, Brazilian\n"
|
||||||
"Language: pt_BR\n"
|
"Language: pt_BR\n"
|
||||||
@@ -1914,10 +1914,26 @@ msgstr "Você está prestes a usar sua conta %(provider_name)s para entrar."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Como etapa final, por favor preencha o seguinte formulário:"
|
msgstr "Como etapa final, por favor preencha o seguinte formulário:"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:24
|
||||||
msgid ", "
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Não é possível analisar o URI {value}, esquema ausente"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:36
|
||||||
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr "Esquema URI '{parts.scheme}' não é permitido. Esquemas permitidos:\n"
|
||||||
|
"{', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Não é possível analisar o URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "more_like_id inválido"
|
msgstr "more_like_id inválido"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Portuguese\n"
|
"Language-Team: Portuguese\n"
|
||||||
"Language: pt_PT\n"
|
"Language: pt_PT\n"
|
||||||
@@ -1913,10 +1913,25 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:24
|
||||||
msgid ", "
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Não foi possível analisar o URI {value}, o esquema está em falta"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:36
|
||||||
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Não foi possível analisar o URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Romanian\n"
|
"Language-Team: Romanian\n"
|
||||||
"Language: ro_RO\n"
|
"Language: ro_RO\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Sunteți pe cale să vă utilizați contul %(provider_name)s pentru a v
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "În etapa finală, vă rugăm să completați următorul formular:"
|
msgstr "În etapa finală, vă rugăm să completați următorul formular:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Imposibil de analizat URI {value}, schemă lipsă"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Imposibil de analizat URI {value}, lipsă locație sau cale"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Schema URI '{parts.scheme}' nu este permisă. Schemele permise: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Nu se poate analiza URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Language: ru_RU\n"
|
"Language: ru_RU\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Вы собираетесь войти через сторонний с
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "На завершение, пожалуйста заполните следующую форму:"
|
msgstr "На завершение, пожалуйста заполните следующую форму:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Не удается распознать URI {value}, отсутствует схема"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Не удалось распознать URI {value}, отсутствует сетевое расположение или путь"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Недопустимая схема URI '{parts.scheme}'. Разрешенные схемы: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Невозможно распознать URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-07 12:33\n"
|
"PO-Revision-Date: 2026-10-06 02:00\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovak\n"
|
"Language-Team: Slovak\n"
|
||||||
"Language: sk_SK\n"
|
"Language: sk_SK\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Chystáte sa na prihlásenie použiť svoj účet %(provider_name)s."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Ako posledný krok vyplňte nasledujúci formulár:"
|
msgstr "Ako posledný krok vyplňte nasledujúci formulár:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Nepodarilo sa analyzovať URI {value}, chýba schéma"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Nepodarilo sa analyzovať URI {value}; chýba sieťové umiestnenie alebo cesta."
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Schéma URI „{parts.scheme}“ nie je povolená. Povolené schémy: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Nepodarilo sa analyzovať URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovenian\n"
|
"Language-Team: Slovenian\n"
|
||||||
"Language: sl_SI\n"
|
"Language: sl_SI\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Za prijavo boste uporabili račun ponudnika %(provider_name)s."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Kot zadnji korak vas prosimo, da izpolnite naslednji obrazec:"
|
msgstr "Kot zadnji korak vas prosimo, da izpolnite naslednji obrazec:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Ni mogoče razčleniti URI-ja {value}, manjka shema"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Ni mogoče razčleniti URI-ja {value}, manjka omrežna lokacija ali pot"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Shema URI '{parts.scheme}' ni dovoljena. Dovoljene sheme: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Ni mogoče razčleniti URI-ja {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Albanian\n"
|
"Language-Team: Albanian\n"
|
||||||
"Language: sq_AL\n"
|
"Language: sq_AL\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr "You are about to use your %(provider_name)s llogari to login."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Si hap i fundit, ju lutemi plotësoni formularin e mëposhtëm:"
|
msgstr "Si hap i fundit, ju lutemi plotësoni formularin e mëposhtëm:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Serbian (Latin)\n"
|
"Language-Team: Serbian (Latin)\n"
|
||||||
"Language: sr_CS\n"
|
"Language: sr_CS\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Uskoro ćete koristiti svoj nalog na %(provider_name)s za prijavu."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Kao poslednji korak, molimo Vas da popunite sledeći obrazac:"
|
msgstr "Kao poslednji korak, molimo Vas da popunite sledeći obrazac:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Nije moguće analizirati URI {value}, nedostaje šema"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Nije moguće analizirati URI {value}, nedostaje mrežan lokacija ili putanja"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Šema URI-ja '{parts.scheme}' nije dozvoljena. Dozvoljene šeme {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Nije moguće analizirati URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Swedish\n"
|
"Language-Team: Swedish\n"
|
||||||
"Language: sv_SE\n"
|
"Language: sv_SE\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Du håller på att använda ditt %(provider_name)s-konto för att logga
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Slutför följande formulär som ett sista steg:"
|
msgstr "Slutför följande formulär som ett sista steg:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Kunde inte tolka URI {value}, schema saknas"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Kunde inte tolka URI {value}, nätverksplats eller sökväg saknas"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI-schemat '{parts.scheme}' är inte tillåtet. Tillåtna scheman: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Kunde inte tolka URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Thai\n"
|
"Language-Team: Thai\n"
|
||||||
"Language: th_TH\n"
|
"Language: th_TH\n"
|
||||||
@@ -1912,8 +1912,23 @@ msgstr ""
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Turkish\n"
|
"Language-Team: Turkish\n"
|
||||||
"Language: tr_TR\n"
|
"Language: tr_TR\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Giriş yapmak için %(provider_name)s hesabınızı kullanmak üzeresini
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Son adım olarak, lütfen aşağıdaki formu doldurun:"
|
msgstr "Son adım olarak, lütfen aşağıdaki formu doldurun:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "URI {value} ayrıştırılamıyor, şema eksik"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "URI {value} ayrıştırılamıyor, ağ konumu veya yol eksik"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI şeması ‘{parts.scheme}’ izin verilmez. İzin verilen şemalar: {‘, ’.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "URI {value} işlenemiyor"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Ukrainian\n"
|
"Language-Team: Ukrainian\n"
|
||||||
"Language: uk_UA\n"
|
"Language: uk_UA\n"
|
||||||
@@ -1913,8 +1913,23 @@ msgstr "Ви збираєтеся використовувати ваш облі
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Як останній крок, будь ласка, заповніть наступну форму:"
|
msgstr "Як останній крок, будь ласка, заповніть наступну форму:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Vietnamese\n"
|
"Language-Team: Vietnamese\n"
|
||||||
"Language: vi_VN\n"
|
"Language: vi_VN\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "Bạn sắp sử dụng tài khoản %(provider_name)s của bạn để
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "Là bước cuối cùng, vui lòng hoàn thành mẫu đơn sau:"
|
msgstr "Là bước cuối cùng, vui lòng hoàn thành mẫu đơn sau:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "Không thể phân tích URI {value}, thiếu giao thức"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "Không thể phân tích URI {value}, thiếu tên miền hoặc đường dẫn"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "Giao thức URI '{parts.scheme}' không được phép. Các giao thức được phép: {', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "Không thể phân tích URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Simplified\n"
|
"Language-Team: Chinese Simplified\n"
|
||||||
"Language: zh_CN\n"
|
"Language: zh_CN\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "您将使用您的 %(provider_name)s 账户登录."
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "作为最后一步,请填写以下表格:"
|
msgstr "作为最后一步,请填写以下表格:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "无法解析 URI {value},缺少scheme"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "无法解析 URI {value},缺少网络位置或路径"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI 格式 '{parts.scheme}' 不支持。支持的格式有:{', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "无法解析 URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-06 15:12+0000\n"
|
"POT-Creation-Date: 2026-10-05 16:26+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-06 15:13\n"
|
"PO-Revision-Date: 2026-10-05 16:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional\n"
|
"Language-Team: Chinese Traditional\n"
|
||||||
"Language: zh_TW\n"
|
"Language: zh_TW\n"
|
||||||
@@ -1913,9 +1913,24 @@ msgstr "即將使用 %(provider_name)s 帳號登入。"
|
|||||||
msgid "As a final step, please complete the following form:"
|
msgid "As a final step, please complete the following form:"
|
||||||
msgstr "最後一步,請填寫以下表單:"
|
msgstr "最後一步,請填寫以下表單:"
|
||||||
|
|
||||||
|
#: documents/validators.py:24
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing scheme"
|
||||||
|
msgstr "無法解析 URI {value},缺少協定"
|
||||||
|
|
||||||
|
#: documents/validators.py:29
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}, missing net location or path"
|
||||||
|
msgstr "無法解析 URI {value},缺少網路位置或路徑"
|
||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid "URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '.join(allowed_schemes)}"
|
||||||
msgstr ""
|
msgstr "URI 協定「{parts.scheme}」不被允許。允許的協定有:{', '.join(allowed_schemes)}"
|
||||||
|
|
||||||
|
#: documents/validators.py:45
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unable to parse URI {value}"
|
||||||
|
msgstr "無法解析 URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:336 documents/views.py:2729
|
#: documents/views.py:336 documents/views.py:2729
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
|
|||||||
@@ -265,6 +265,52 @@ def get_page_count_for_pdf(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_pdf_first_page_size_points(
|
||||||
|
path: Path,
|
||||||
|
log: logging.Logger | None = None,
|
||||||
|
) -> tuple[float, float] | None:
|
||||||
|
"""Return the first page's (width, height) in PDF points, post-rotation.
|
||||||
|
|
||||||
|
Uses the CropBox (MediaBox if absent), which must match pdftoppm's
|
||||||
|
``-cropbox`` or the computed DPI targets the wrong box.
|
||||||
|
|
||||||
|
Swaps width and height for 90/270 rotation. ``page.rotation`` resolves
|
||||||
|
inherited and negative ``/Rotate`` values, a raw lookup does not.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
path:
|
||||||
|
Absolute path to the PDF file.
|
||||||
|
log:
|
||||||
|
Logger for warnings. Falls back to the module-level logger when omitted.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple[float, float] | None
|
||||||
|
``(width_points, height_points)``, or ``None`` if the file cannot be
|
||||||
|
opened, has no pages, or the page box is degenerate.
|
||||||
|
"""
|
||||||
|
import pikepdf
|
||||||
|
|
||||||
|
_log = log or logger
|
||||||
|
try:
|
||||||
|
with pikepdf.Pdf.open(path) as pdf:
|
||||||
|
if len(pdf.pages) == 0:
|
||||||
|
return None
|
||||||
|
page = pdf.pages[0]
|
||||||
|
llx, lly, urx, ury = (float(v) for v in page.cropbox)
|
||||||
|
width = abs(urx - llx)
|
||||||
|
height = abs(ury - lly)
|
||||||
|
if width <= 0 or height <= 0:
|
||||||
|
return None
|
||||||
|
if page.rotation in (90, 270):
|
||||||
|
width, height = height, width
|
||||||
|
return width, height
|
||||||
|
except Exception as e:
|
||||||
|
_log.warning("Could not determine PDF page size for %s: %s", path, e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def extract_pdf_metadata(
|
def extract_pdf_metadata(
|
||||||
document_path: Path,
|
document_path: Path,
|
||||||
log: logging.Logger | None = None,
|
log: logging.Logger | None = None,
|
||||||
|
|||||||
@@ -986,8 +986,6 @@ GNUPG_HOME = os.getenv("HOME", "/tmp")
|
|||||||
|
|
||||||
# Convert is part of the ImageMagick package
|
# Convert is part of the ImageMagick package
|
||||||
CONVERT_BINARY = os.getenv("PAPERLESS_CONVERT_BINARY", "convert")
|
CONVERT_BINARY = os.getenv("PAPERLESS_CONVERT_BINARY", "convert")
|
||||||
CONVERT_TMPDIR = os.getenv("PAPERLESS_CONVERT_TMPDIR")
|
|
||||||
CONVERT_MEMORY_LIMIT = os.getenv("PAPERLESS_CONVERT_MEMORY_LIMIT")
|
|
||||||
|
|
||||||
GS_BINARY = os.getenv("PAPERLESS_GS_BINARY", "gs")
|
GS_BINARY = os.getenv("PAPERLESS_GS_BINARY", "gs")
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from ocrmypdf import SubprocessOutputError
|
from ocrmypdf import SubprocessOutputError
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from documents.parsers import ParseError
|
from documents.parsers import ParseError
|
||||||
from documents.parsers import run_convert
|
from documents.parsers import rasterize_pdf_page_to_png
|
||||||
from paperless.models import ModeChoices
|
from paperless.models import ModeChoices
|
||||||
from paperless.parsers import ParserProtocol
|
from paperless.parsers import ParserProtocol
|
||||||
from paperless.parsers.tesseract import RasterisedDocumentParser
|
from paperless.parsers.tesseract import RasterisedDocumentParser
|
||||||
@@ -280,24 +281,71 @@ class TestGetThumbnail:
|
|||||||
)
|
)
|
||||||
assert thumb.is_file()
|
assert thumb.is_file()
|
||||||
|
|
||||||
def test_thumbnail_fallback_on_convert_error(
|
@pytest.mark.parametrize(
|
||||||
|
("filename", "expected_height"),
|
||||||
|
[
|
||||||
|
pytest.param("simple-digital.pdf", 647, id="portrait-letter"),
|
||||||
|
pytest.param("rotated.pdf", 386, id="landscape"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_thumbnail_is_correct_format_and_size(
|
||||||
|
self,
|
||||||
|
tesseract_parser: RasterisedDocumentParser,
|
||||||
|
tesseract_samples_dir: Path,
|
||||||
|
filename: str,
|
||||||
|
expected_height: int,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A portrait or landscape PDF
|
||||||
|
WHEN:
|
||||||
|
- A thumbnail is generated
|
||||||
|
THEN:
|
||||||
|
- A 500px wide WebP keeping the page's aspect ratio
|
||||||
|
"""
|
||||||
|
thumb = tesseract_parser.get_thumbnail(
|
||||||
|
tesseract_samples_dir / filename,
|
||||||
|
"application/pdf",
|
||||||
|
)
|
||||||
|
with Image.open(thumb) as im:
|
||||||
|
assert im.format == "WEBP"
|
||||||
|
assert im.width == 500
|
||||||
|
assert im.height == pytest.approx(expected_height, abs=2)
|
||||||
|
|
||||||
|
def test_thumbnail_fallback_on_pdftoppm_error(
|
||||||
self,
|
self,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
tesseract_parser: RasterisedDocumentParser,
|
tesseract_parser: RasterisedDocumentParser,
|
||||||
tesseract_samples_dir: Path,
|
tesseract_samples_dir: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
def _raise_on_pdf(input_file, output_file, **kwargs) -> None:
|
"""
|
||||||
if ".pdf" in str(input_file):
|
GIVEN:
|
||||||
|
- Rasterizing the original PDF fails
|
||||||
|
WHEN:
|
||||||
|
- A thumbnail is generated
|
||||||
|
THEN:
|
||||||
|
- The PDF is repaired with qpdf and a real thumbnail is rendered
|
||||||
|
"""
|
||||||
|
original = tesseract_samples_dir / "simple-digital.pdf"
|
||||||
|
|
||||||
|
def _fail_on_original(in_path: Path, out_path: Path, **kwargs) -> None:
|
||||||
|
if in_path == original:
|
||||||
raise ParseError("Does not compute.")
|
raise ParseError("Does not compute.")
|
||||||
run_convert(input_file=input_file, output_file=output_file, **kwargs)
|
rasterize_pdf_page_to_png(in_path, out_path, **kwargs)
|
||||||
|
|
||||||
mocker.patch("documents.parsers.run_convert", side_effect=_raise_on_pdf)
|
rasterize = mocker.patch(
|
||||||
|
"documents.parsers.rasterize_pdf_page_to_png",
|
||||||
thumb = tesseract_parser.get_thumbnail(
|
side_effect=_fail_on_original,
|
||||||
tesseract_samples_dir / "simple-digital.pdf",
|
|
||||||
"application/pdf",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
thumb = tesseract_parser.get_thumbnail(original, "application/pdf")
|
||||||
|
|
||||||
|
assert rasterize.call_count == 2
|
||||||
assert thumb.is_file()
|
assert thumb.is_file()
|
||||||
|
assert thumb.name == "convert_qpdf.webp"
|
||||||
|
with Image.open(thumb) as im:
|
||||||
|
assert im.format == "WEBP"
|
||||||
|
assert im.width == 500
|
||||||
|
|
||||||
def test_thumbnail_encrypted_pdf(
|
def test_thumbnail_encrypted_pdf(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import codecs
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pikepdf
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from paperless.parsers.utils import get_pdf_first_page_size_points
|
||||||
from paperless.parsers.utils import is_tagged_pdf
|
from paperless.parsers.utils import is_tagged_pdf
|
||||||
from paperless.parsers.utils import pdf_born_digital_text
|
from paperless.parsers.utils import pdf_born_digital_text
|
||||||
from paperless.parsers.utils import post_process_text
|
from paperless.parsers.utils import post_process_text
|
||||||
@@ -70,6 +72,149 @@ class TestIsTaggedPdf:
|
|||||||
assert is_tagged_pdf(bad) is False
|
assert is_tagged_pdf(bad) is False
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetPdfFirstPageSizePoints:
|
||||||
|
@staticmethod
|
||||||
|
def _write_pdf(
|
||||||
|
path: Path,
|
||||||
|
*,
|
||||||
|
media_box: tuple[float, float, float, float] = (0, 0, 600, 800),
|
||||||
|
crop_box: tuple[float, float, float, float] | None = None,
|
||||||
|
page_rotate: int | None = None,
|
||||||
|
inherited_rotate: int | None = None,
|
||||||
|
) -> Path:
|
||||||
|
pdf = pikepdf.new()
|
||||||
|
pdf.add_blank_page(page_size=(media_box[2], media_box[3]))
|
||||||
|
page = pdf.pages[0]
|
||||||
|
page.obj.MediaBox = pikepdf.Array(media_box)
|
||||||
|
if crop_box is not None:
|
||||||
|
page.obj.CropBox = pikepdf.Array(crop_box)
|
||||||
|
if page_rotate is not None:
|
||||||
|
page.obj.Rotate = page_rotate
|
||||||
|
if inherited_rotate is not None:
|
||||||
|
pdf.Root.Pages.Rotate = inherited_rotate
|
||||||
|
pdf.save(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def test_letter_sample(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A US Letter sample PDF with no CropBox and no rotation
|
||||||
|
WHEN:
|
||||||
|
- The first page size is requested
|
||||||
|
THEN:
|
||||||
|
- The MediaBox size in points is returned
|
||||||
|
"""
|
||||||
|
assert get_pdf_first_page_size_points(SAMPLES / "simple-digital.pdf") == (
|
||||||
|
612.0,
|
||||||
|
792.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("rotate", "expected"),
|
||||||
|
[
|
||||||
|
pytest.param(0, (600.0, 800.0), id="rotate-0"),
|
||||||
|
pytest.param(90, (800.0, 600.0), id="rotate-90"),
|
||||||
|
pytest.param(180, (600.0, 800.0), id="rotate-180"),
|
||||||
|
pytest.param(270, (800.0, 600.0), id="rotate-270"),
|
||||||
|
pytest.param(-90, (800.0, 600.0), id="rotate-negative-90"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_page_rotation_swaps_dimensions(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
rotate: int,
|
||||||
|
expected: tuple[float, float],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A portrait PDF page with /Rotate set directly on the page
|
||||||
|
WHEN:
|
||||||
|
- The first page size is requested
|
||||||
|
THEN:
|
||||||
|
- Width and height are swapped for quarter-turn rotations only
|
||||||
|
"""
|
||||||
|
pdf_path = self._write_pdf(tmp_path / "rotated.pdf", page_rotate=rotate)
|
||||||
|
assert get_pdf_first_page_size_points(pdf_path) == expected
|
||||||
|
|
||||||
|
def test_inherited_rotation_swaps_dimensions(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A page inheriting /Rotate 90 from the /Pages node
|
||||||
|
WHEN:
|
||||||
|
- The first page size is requested
|
||||||
|
THEN:
|
||||||
|
- Width and height are swapped
|
||||||
|
"""
|
||||||
|
pdf_path = self._write_pdf(tmp_path / "inherited.pdf", inherited_rotate=90)
|
||||||
|
assert get_pdf_first_page_size_points(pdf_path) == (800.0, 600.0)
|
||||||
|
|
||||||
|
def test_crop_box_preferred_over_media_box(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A PDF page with a CropBox smaller than its MediaBox
|
||||||
|
WHEN:
|
||||||
|
- The first page size is requested
|
||||||
|
THEN:
|
||||||
|
- The CropBox size is returned
|
||||||
|
"""
|
||||||
|
pdf_path = self._write_pdf(
|
||||||
|
tmp_path / "cropped.pdf",
|
||||||
|
crop_box=(50, 100, 350, 500),
|
||||||
|
)
|
||||||
|
assert get_pdf_first_page_size_points(pdf_path) == (300.0, 400.0)
|
||||||
|
|
||||||
|
def test_degenerate_box_returns_none(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A PDF page whose box has zero width
|
||||||
|
WHEN:
|
||||||
|
- The first page size is requested
|
||||||
|
THEN:
|
||||||
|
- None is returned
|
||||||
|
"""
|
||||||
|
pdf_path = self._write_pdf(
|
||||||
|
tmp_path / "degenerate.pdf",
|
||||||
|
media_box=(0, 0, 600, 800),
|
||||||
|
crop_box=(100, 0, 100, 800),
|
||||||
|
)
|
||||||
|
assert get_pdf_first_page_size_points(pdf_path) is None
|
||||||
|
|
||||||
|
def test_nonexistent_path_returns_none(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A path that does not exist
|
||||||
|
WHEN:
|
||||||
|
- The first page size is requested
|
||||||
|
THEN:
|
||||||
|
- None is returned and nothing is raised
|
||||||
|
"""
|
||||||
|
assert get_pdf_first_page_size_points(Path("/nonexistent/file.pdf")) is None
|
||||||
|
|
||||||
|
def test_corrupt_pdf_returns_none(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A file that is not a PDF
|
||||||
|
WHEN:
|
||||||
|
- The first page size is requested
|
||||||
|
THEN:
|
||||||
|
- None is returned and nothing is raised
|
||||||
|
"""
|
||||||
|
bad = tmp_path / "bad.pdf"
|
||||||
|
bad.write_bytes(b"not a pdf")
|
||||||
|
assert get_pdf_first_page_size_points(bad) is None
|
||||||
|
|
||||||
|
def test_encrypted_pdf_returns_none(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A password protected PDF
|
||||||
|
WHEN:
|
||||||
|
- The first page size is requested
|
||||||
|
THEN:
|
||||||
|
- None is returned and nothing is raised
|
||||||
|
"""
|
||||||
|
assert get_pdf_first_page_size_points(SAMPLES / "encrypted.pdf") is None
|
||||||
|
|
||||||
|
|
||||||
class TestPostProcessText:
|
class TestPostProcessText:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("source", "expected"),
|
("source", "expected"),
|
||||||
|
|||||||
Reference in new issue
Block a user