From cd38c39908ea2013d6a7f63f55534b583c4a2c2d Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:42:20 -0800 Subject: [PATCH 1/8] Resets to -dev version string --- src-ui/src/environments/environment.prod.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ui/src/environments/environment.prod.ts b/src-ui/src/environments/environment.prod.ts index 859878b0a..a61dc40ee 100644 --- a/src-ui/src/environments/environment.prod.ts +++ b/src-ui/src/environments/environment.prod.ts @@ -5,7 +5,7 @@ export const environment = { apiBaseUrl: document.baseURI + 'api/', apiVersion: '3', appTitle: 'Paperless-ngx', - version: '2.1.2', + version: '2.1.2-dev', webSocketHost: window.location.host, webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:', webSocketBaseUrl: base_url.pathname + 'ws/', From 45109026774b8d1d5c5a9ac14a9d850f8eb11069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Bogda=C5=82?= Date: Thu, 14 Dec 2023 16:39:49 +0100 Subject: [PATCH 2/8] Fix: Don't attempt to parse none objects during date searching --- src/documents/index.py | 3 ++- src/documents/tests/test_api_search.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/documents/index.py b/src/documents/index.py index 34b2a56c3..ebfe40e18 100644 --- a/src/documents/index.py +++ b/src/documents/index.py @@ -3,6 +3,7 @@ import math import os from collections import Counter from contextlib import contextmanager +from datetime import datetime from typing import Optional from dateutil.parser import isoparse @@ -371,7 +372,7 @@ class LocalDateParser(English): if isinstance(d, timespan): d.start = self.reverse_timezone_offset(d.start) d.end = self.reverse_timezone_offset(d.end) - else: + elif isinstance(d, datetime): d = self.reverse_timezone_offset(d) return d diff --git a/src/documents/tests/test_api_search.py b/src/documents/tests/test_api_search.py index 84461bb35..4cd1a367c 100644 --- a/src/documents/tests/test_api_search.py +++ b/src/documents/tests/test_api_search.py @@ -455,6 +455,31 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): # Assert subset in results self.assertDictEqual(result, {**result, **subset}) + def test_search_added_invalid_date(self): + """ + GIVEN: + - One document added right now + WHEN: + - Query with invalid added date + THEN: + - No documents returned + """ + d1 = Document.objects.create( + title="invoice", + content="the thing i bought at a shop and paid with bank account", + checksum="A", + pk=1, + ) + + with index.open_index_writer() as writer: + index.update_document(writer, d1) + + response = self.client.get("/api/documents/?query=added:invalid-date") + results = response.data["results"] + + # Expect 0 document returned + self.assertEqual(len(results), 0) + @mock.patch("documents.index.autocomplete") def test_search_autocomplete_limits(self, m): """ From 72000cac362e85495a2e17a1e562d351b90a4f30 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:05:36 -0800 Subject: [PATCH 3/8] Fix: show errors for select dropdowns (#4979) --- .../consumption-template-edit-dialog.component.html | 2 +- .../components/common/input/select/select.component.html | 5 ++++- .../components/common/input/select/select.component.scss | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html index 920026448..f537527eb 100644 --- a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html +++ b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html @@ -17,7 +17,7 @@
Filters

Process documents that match all filters specified below.

- + diff --git a/src-ui/src/app/components/common/input/select/select.component.html b/src-ui/src/app/components/common/input/select/select.component.html index 194c40904..844ea1932 100644 --- a/src-ui/src/app/components/common/input/select/select.component.html +++ b/src-ui/src/app/components/common/input/select/select.component.html @@ -9,7 +9,7 @@
-
+
+
+ {{error}} +
{{hint}} Suggestions:  diff --git a/src-ui/src/app/components/common/input/select/select.component.scss b/src-ui/src/app/components/common/input/select/select.component.scss index 2ed6995c8..7cfe14fdc 100644 --- a/src-ui/src/app/components/common/input/select/select.component.scss +++ b/src-ui/src/app/components/common/input/select/select.component.scss @@ -17,3 +17,12 @@ font-style: italic; opacity: .75; } + +::ng-deep .is-invalid ng-select .ng-select-container input { + // replicate bootstrap + padding-right: calc(1.5em + 0.75rem) !important; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") !important; + background-repeat: no-repeat !important; + background-position: right calc(0.375em + 0.1875rem) center !important; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) !important; +} From 92a920021d1c67a16024fa777f9e53a83845e67a Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Thu, 14 Dec 2023 11:20:47 -0800 Subject: [PATCH 4/8] Apply user arguments even in the case of the safe fallback to forcing OCR (#4981) --- docs/configuration.md | 2 +- src/paperless_tesseract/parsers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 87d992443..b2e479f98 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -733,7 +733,7 @@ they use underscores instead of dashes. Paperless has been tested to work with the OCR options provided above. There are many options that are incompatible with each other, so specifying invalid options may prevent paperless from consuming - any documents. + any documents. Use with caution! Specify arguments as a JSON dictionary. Keep note of lower case booleans and double quoted parameter names and strings. Examples: diff --git a/src/paperless_tesseract/parsers.py b/src/paperless_tesseract/parsers.py index babcf6bcf..46d106bd7 100644 --- a/src/paperless_tesseract/parsers.py +++ b/src/paperless_tesseract/parsers.py @@ -254,7 +254,7 @@ class RasterisedDocumentParser(DocumentParser): f"Image DPI of {ocrmypdf_args['image_dpi']} is low, OCR may fail", ) - if settings.OCR_USER_ARGS and not safe_fallback: + if settings.OCR_USER_ARGS: try: user_args = json.loads(settings.OCR_USER_ARGS) ocrmypdf_args = {**ocrmypdf_args, **user_args} From be2de4f15dd6c8942c0ba9f48f2d0732f86d2d7f Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Thu, 14 Dec 2023 19:23:39 -0800 Subject: [PATCH 5/8] Fixes export of custom field instances during a split manifest export (#4984) --- .../management/commands/document_exporter.py | 38 +++++++++++-------- .../tests/test_management_exporter.py | 3 ++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index 4f3cb937a..8913b1b6f 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -238,18 +238,6 @@ class Command(BaseCommand): serializers.serialize("json", StoragePath.objects.all()), ) - notes = json.loads( - serializers.serialize("json", Note.objects.all()), - ) - if not self.split_manifest: - manifest += notes - - documents = Document.objects.order_by("id") - document_map = {d.pk: d for d in documents} - document_manifest = json.loads(serializers.serialize("json", documents)) - if not self.split_manifest: - manifest += document_manifest - manifest += json.loads( serializers.serialize("json", MailAccount.objects.all()), ) @@ -303,10 +291,24 @@ class Command(BaseCommand): serializers.serialize("json", CustomField.objects.all()), ) + # These are treated specially and included in the per-document manifest + # if that setting is enabled. Otherwise, they are just exported to the bulk + # manifest + documents = Document.objects.order_by("id") + document_map: dict[int, Document] = {d.pk: d for d in documents} + document_manifest = json.loads(serializers.serialize("json", documents)) + + notes = json.loads( + serializers.serialize("json", Note.objects.all()), + ) + + custom_field_instances = json.loads( + serializers.serialize("json", CustomFieldInstance.objects.all()), + ) if not self.split_manifest: - manifest += json.loads( - serializers.serialize("json", CustomFieldInstance.objects.all()), - ) + manifest += document_manifest + manifest += notes + manifest += custom_field_instances # 3. Export files from each document for index, document_dict in tqdm.tqdm( @@ -412,6 +414,12 @@ class Command(BaseCommand): notes, ), ) + content += list( + filter( + lambda d: d["fields"]["document"] == document_dict["pk"], + custom_field_instances, + ), + ) manifest_name.write_text( json.dumps(content, indent=2, ensure_ascii=False), encoding="utf-8", diff --git a/src/documents/tests/test_management_exporter.py b/src/documents/tests/test_management_exporter.py index b4dc5720a..54bb6f34c 100644 --- a/src/documents/tests/test_management_exporter.py +++ b/src/documents/tests/test_management_exporter.py @@ -646,10 +646,13 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase): with paperless_environment(): self.assertEqual(Document.objects.count(), 4) + self.assertEqual(CustomFieldInstance.objects.count(), 1) Document.objects.all().delete() + CustomFieldInstance.objects.all().delete() self.assertEqual(Document.objects.count(), 0) call_command("document_importer", "--no-progress-bar", self.target) self.assertEqual(Document.objects.count(), 4) + self.assertEqual(CustomFieldInstance.objects.count(), 1) def test_folder_prefix(self): """ From 122e4141b0722262ce97c3bed739ab05d349a2ad Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Fri, 15 Dec 2023 09:17:25 -0800 Subject: [PATCH 6/8] Fix: Document metadata is lost during barcode splitting (#4982) * Fixes barcode splitting dropping metadata that might be needed for the round 2 --- src/documents/barcodes.py | 54 ++-- src/documents/tasks.py | 2 +- src/documents/tests/test_barcodes.py | 402 +++++++++------------------ src/documents/tests/utils.py | 12 +- 4 files changed, 168 insertions(+), 302 deletions(-) diff --git a/src/documents/barcodes.py b/src/documents/barcodes.py index cac02e3e9..3e2b309b6 100644 --- a/src/documents/barcodes.py +++ b/src/documents/barcodes.py @@ -14,6 +14,8 @@ from pikepdf import Pdf from PIL import Image from documents.converters import convert_from_tiff_to_pdf +from documents.data_models import ConsumableDocument +from documents.data_models import DocumentMetadataOverrides from documents.data_models import DocumentSource from documents.utils import copy_basic_file_stats from documents.utils import copy_file_with_basic_stats @@ -53,6 +55,7 @@ class BarcodeReader: self.mime: Final[str] = mime_type self.pdf_file: Path = self.file self.barcodes: list[Barcode] = [] + self._tiff_conversion_done = False self.temp_dir: Optional[tempfile.TemporaryDirectory] = None if settings.CONSUMER_BARCODE_TIFF_SUPPORT: @@ -150,12 +153,14 @@ class BarcodeReader: def convert_from_tiff_to_pdf(self): """ - May convert a TIFF image into a PDF, if the input is a TIFF + May convert a TIFF image into a PDF, if the input is a TIFF and + the TIFF has not been made into a PDF """ # Nothing to do, pdf_file is already assigned correctly - if self.mime != "image/tiff": + if self.mime != "image/tiff" or self._tiff_conversion_done: return + self._tiff_conversion_done = True self.pdf_file = convert_from_tiff_to_pdf(self.file, Path(self.temp_dir.name)) def detect(self) -> None: @@ -167,6 +172,9 @@ class BarcodeReader: if self.barcodes: return + # No op if not a TIFF + self.convert_from_tiff_to_pdf() + # Choose the library for reading if settings.CONSUMER_BARCODE_SCANNER == "PYZBAR": reader = self.read_barcodes_pyzbar @@ -240,7 +248,7 @@ class BarcodeReader: """ document_paths = [] - fname = self.file.with_suffix("").name + fname = self.file.stem with Pdf.open(self.pdf_file) as input_pdf: # Start with an empty document current_document: list[Page] = [] @@ -290,7 +298,7 @@ class BarcodeReader: def separate( self, source: DocumentSource, - override_name: Optional[str] = None, + overrides: DocumentMetadataOverrides, ) -> bool: """ Separates the document, based on barcodes and configuration, creating new @@ -316,27 +324,23 @@ class BarcodeReader: logger.warning("No pages to split on!") return False - # Create the split documents - doc_paths = self.separate_pages(separator_pages) + tmp_dir = Path(tempfile.mkdtemp(prefix="paperless-barcode-split-")).resolve() - # Save the new documents to correct folder - if source != DocumentSource.ConsumeFolder: - # The given file is somewhere in SCRATCH_DIR, - # and new documents must be moved to the CONSUMPTION_DIR - # for the consumer to notice them - save_to_dir = settings.CONSUMPTION_DIR - else: - # The given file is somewhere in CONSUMPTION_DIR, - # and may be some levels down for recursive tagging - # so use the file's parent to preserve any metadata - save_to_dir = self.file.parent + from documents import tasks - for idx, document_path in enumerate(doc_paths): - if override_name is not None: - newname = f"{idx}_{override_name}" - dest = save_to_dir / newname - else: - dest = save_to_dir - logger.info(f"Saving {document_path} to {dest}") - copy_file_with_basic_stats(document_path, dest) + # Create the split document tasks + for new_document in self.separate_pages(separator_pages): + copy_file_with_basic_stats(new_document, tmp_dir / new_document.name) + + tasks.consume_file.delay( + ConsumableDocument( + # Same source, for templates + source=source, + # Can't use same folder or the consume might grab it again + original_file=(tmp_dir / new_document.name).resolve(), + ), + # All the same metadata + overrides, + ) + logger.info("Barcode splitting complete!") return True diff --git a/src/documents/tasks.py b/src/documents/tasks.py index 10a44a8fe..d0728a719 100644 --- a/src/documents/tasks.py +++ b/src/documents/tasks.py @@ -140,7 +140,7 @@ def consume_file( with BarcodeReader(input_doc.original_file, input_doc.mime_type) as reader: if settings.CONSUMER_ENABLE_BARCODES and reader.separate( input_doc.source, - overrides.filename, + overrides, ): # notify the sender, otherwise the progress bar # in the UI stays stuck diff --git a/src/documents/tests/test_barcodes.py b/src/documents/tests/test_barcodes.py index 70f7807cc..e4d8ccc57 100644 --- a/src/documents/tests/test_barcodes.py +++ b/src/documents/tests/test_barcodes.py @@ -1,5 +1,4 @@ import shutil -from pathlib import Path from unittest import mock import pytest @@ -11,10 +10,13 @@ from documents import tasks from documents.barcodes import BarcodeReader from documents.consumer import ConsumerError from documents.data_models import ConsumableDocument +from documents.data_models import DocumentMetadataOverrides from documents.data_models import DocumentSource from documents.models import Document from documents.tests.utils import DirectoriesMixin +from documents.tests.utils import DocumentConsumeDelayMixin from documents.tests.utils import FileSystemAssertsMixin +from documents.tests.utils import SampleDirMixin try: import zxingcpp # noqa: F401 @@ -25,11 +27,7 @@ except ImportError: @override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR") -class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase): - SAMPLE_DIR = Path(__file__).parent / "samples" - - BARCODE_SAMPLE_DIR = SAMPLE_DIR / "barcodes" - +class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, SampleDirMixin, TestCase): def test_scan_file_for_separating_barcodes(self): """ GIVEN: @@ -48,6 +46,46 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase): self.assertEqual(reader.pdf_file, test_file) self.assertDictEqual(separator_page_numbers, {0: False}) + @override_settings( + CONSUMER_BARCODE_TIFF_SUPPORT=True, + ) + def test_scan_tiff_for_separating_barcodes(self): + """ + GIVEN: + - TIFF image containing barcodes + WHEN: + - Consume task returns + THEN: + - The file was split + """ + test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.tiff" + + with BarcodeReader(test_file, "image/tiff") as reader: + reader.detect() + separator_page_numbers = reader.get_separation_pages() + + self.assertDictEqual(separator_page_numbers, {1: False}) + + @override_settings( + CONSUMER_BARCODE_TIFF_SUPPORT=True, + ) + def test_scan_tiff_with_alpha_for_separating_barcodes(self): + """ + GIVEN: + - TIFF image containing barcodes + WHEN: + - Consume task returns + THEN: + - The file was split + """ + test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle-alpha.tiff" + + with BarcodeReader(test_file, "image/tiff") as reader: + reader.detect() + separator_page_numbers = reader.get_separation_pages() + + self.assertDictEqual(separator_page_numbers, {1: False}) + def test_scan_file_for_separating_barcodes_none_present(self): """ GIVEN: @@ -285,6 +323,28 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase): self.assertGreater(len(reader.barcodes), 0) self.assertDictEqual(separator_page_numbers, {1: False}) + def test_scan_file_for_separating_barcodes_password(self): + """ + GIVEN: + - Password protected PDF + WHEN: + - File is scanned for barcode + THEN: + - Scanning handles the exception without crashing + """ + test_file = self.SAMPLE_DIR / "password-is-test.pdf" + with self.assertLogs("paperless.barcodes", level="WARNING") as cm: + with BarcodeReader(test_file, "application/pdf") as reader: + reader.detect() + warning = cm.output[0] + expected_str = "WARNING:paperless.barcodes:File is likely password protected, not checking for barcodes" + self.assertTrue(warning.startswith(expected_str)) + + separator_page_numbers = reader.get_separation_pages() + + self.assertEqual(reader.pdf_file, test_file) + self.assertDictEqual(separator_page_numbers, {}) + def test_separate_pages(self): """ GIVEN: @@ -332,8 +392,12 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase): with self.assertLogs("paperless.barcodes", level="WARNING") as cm: with BarcodeReader(test_file, "application/pdf") as reader: - success = reader.separate(DocumentSource.ApiUpload) - self.assertFalse(success) + self.assertFalse( + reader.separate( + DocumentSource.ApiUpload, + DocumentMetadataOverrides(), + ), + ) self.assertEqual( cm.output, [ @@ -341,215 +405,6 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase): ], ) - def test_save_to_dir_given_name(self): - """ - GIVEN: - - File to save to a directory - - There is a name override - WHEN: - - The file is saved - THEN: - - The file exists - """ - test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf" - with BarcodeReader(test_file, "application/pdf") as reader: - reader.separate(DocumentSource.ApiUpload, "newname.pdf") - - self.assertEqual(reader.pdf_file, test_file) - target_file1 = settings.CONSUMPTION_DIR / "0_newname.pdf" - target_file2 = settings.CONSUMPTION_DIR / "1_newname.pdf" - self.assertIsFile(target_file1) - self.assertIsFile(target_file2) - - def test_barcode_splitter_api_upload(self): - """ - GIVEN: - - Input file containing barcodes - WHEN: - - Input file is split on barcodes - THEN: - - Correct number of files produced - """ - sample_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf" - test_file = settings.SCRATCH_DIR / "patch-code-t-middle.pdf" - shutil.copy(sample_file, test_file) - - with BarcodeReader(test_file, "application/pdf") as reader: - reader.separate(DocumentSource.ApiUpload) - - self.assertEqual(reader.pdf_file, test_file) - - target_file1 = ( - settings.CONSUMPTION_DIR / "patch-code-t-middle_document_0.pdf" - ) - - target_file2 = ( - settings.CONSUMPTION_DIR / "patch-code-t-middle_document_1.pdf" - ) - - self.assertIsFile(target_file1) - self.assertIsFile(target_file2) - - def test_barcode_splitter_consume_dir(self): - """ - GIVEN: - - Input file containing barcodes - WHEN: - - Input file is split on barcodes - THEN: - - Correct number of files produced - """ - sample_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf" - test_file = settings.CONSUMPTION_DIR / "patch-code-t-middle.pdf" - shutil.copy(sample_file, test_file) - - with BarcodeReader(test_file, "application/pdf") as reader: - reader.detect() - reader.separate(DocumentSource.ConsumeFolder) - - self.assertEqual(reader.pdf_file, test_file) - - target_file1 = ( - settings.CONSUMPTION_DIR / "patch-code-t-middle_document_0.pdf" - ) - - target_file2 = ( - settings.CONSUMPTION_DIR / "patch-code-t-middle_document_1.pdf" - ) - - self.assertIsFile(target_file1) - self.assertIsFile(target_file2) - - def test_barcode_splitter_consume_dir_recursive(self): - """ - GIVEN: - - Input file containing barcodes - - Input file is within a directory structure of the consume folder - WHEN: - - Input file is split on barcodes - THEN: - - Correct number of files produced - - Output files are within the same directory structure - """ - sample_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf" - test_file = ( - settings.CONSUMPTION_DIR / "tag1" / "tag2" / "patch-code-t-middle.pdf" - ) - test_file.parent.mkdir(parents=True) - shutil.copy(sample_file, test_file) - - with BarcodeReader(test_file, "application/pdf") as reader: - reader.separate(DocumentSource.ConsumeFolder) - - self.assertEqual(reader.pdf_file, test_file) - - target_file1 = ( - settings.CONSUMPTION_DIR - / "tag1" - / "tag2" - / "patch-code-t-middle_document_0.pdf" - ) - - target_file2 = ( - settings.CONSUMPTION_DIR - / "tag1" - / "tag2" - / "patch-code-t-middle_document_1.pdf" - ) - - self.assertIsFile(target_file1) - self.assertIsFile(target_file2) - - @override_settings(CONSUMER_ENABLE_BARCODES=True) - def test_consume_barcode_file(self): - """ - GIVEN: - - Input file with barcodes given to consume task - WHEN: - - Consume task returns - THEN: - - The file was split - """ - test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf" - - dst = settings.SCRATCH_DIR / "patch-code-t-middle.pdf" - shutil.copy(test_file, dst) - - with mock.patch("documents.tasks.async_to_sync"): - self.assertEqual( - tasks.consume_file( - ConsumableDocument( - source=DocumentSource.ConsumeFolder, - original_file=dst, - ), - None, - ), - "File successfully split", - ) - - @override_settings( - CONSUMER_ENABLE_BARCODES=True, - CONSUMER_BARCODE_TIFF_SUPPORT=True, - ) - def test_consume_barcode_tiff_file(self): - """ - GIVEN: - - TIFF image containing barcodes - WHEN: - - Consume task returns - THEN: - - The file was split - """ - test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.tiff" - - dst = settings.SCRATCH_DIR / "patch-code-t-middle.tiff" - shutil.copy(test_file, dst) - - with mock.patch("documents.tasks.async_to_sync"): - self.assertEqual( - tasks.consume_file( - ConsumableDocument( - source=DocumentSource.ConsumeFolder, - original_file=dst, - ), - None, - ), - "File successfully split", - ) - self.assertIsNotFile(dst) - - @override_settings( - CONSUMER_ENABLE_BARCODES=True, - CONSUMER_BARCODE_TIFF_SUPPORT=True, - ) - def test_consume_barcode_tiff_file_with_alpha(self): - """ - GIVEN: - - TIFF image containing barcodes - - TIFF image has an alpha layer - WHEN: - - Consume task handles the alpha layer and returns - THEN: - - The file was split without issue - """ - test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle-alpha.tiff" - - dst = settings.SCRATCH_DIR / "patch-code-t-middle.tiff" - shutil.copy(test_file, dst) - - with mock.patch("documents.tasks.async_to_sync"): - self.assertEqual( - tasks.consume_file( - ConsumableDocument( - source=DocumentSource.ConsumeFolder, - original_file=dst, - ), - None, - ), - "File successfully split", - ) - self.assertIsNotFile(dst) - @override_settings( CONSUMER_ENABLE_BARCODES=True, CONSUMER_BARCODE_TIFF_SUPPORT=True, @@ -597,60 +452,6 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase): self.assertIsNone(kwargs["override_document_type_id"]) self.assertIsNone(kwargs["override_tag_ids"]) - @override_settings( - CONSUMER_ENABLE_BARCODES=True, - CONSUMER_BARCODE_TIFF_SUPPORT=True, - ) - def test_consume_barcode_supported_no_extension_file(self): - """ - GIVEN: - - TIFF image containing barcodes - - TIFF file is given without extension - WHEN: - - Consume task returns - THEN: - - The file was split - """ - test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.tiff" - - dst = settings.SCRATCH_DIR / "patch-code-t-middle" - shutil.copy(test_file, dst) - - with mock.patch("documents.tasks.async_to_sync"): - self.assertEqual( - tasks.consume_file( - ConsumableDocument( - source=DocumentSource.ConsumeFolder, - original_file=dst, - ), - None, - ), - "File successfully split", - ) - self.assertIsNotFile(dst) - - def test_scan_file_for_separating_barcodes_password(self): - """ - GIVEN: - - Password protected PDF - WHEN: - - File is scanned for barcode - THEN: - - Scanning handles the exception without crashing - """ - test_file = self.SAMPLE_DIR / "password-is-test.pdf" - with self.assertLogs("paperless.barcodes", level="WARNING") as cm: - with BarcodeReader(test_file, "application/pdf") as reader: - reader.detect() - warning = cm.output[0] - expected_str = "WARNING:paperless.barcodes:File is likely password protected, not checking for barcodes" - self.assertTrue(warning.startswith(expected_str)) - - separator_page_numbers = reader.get_separation_pages() - - self.assertEqual(reader.pdf_file, test_file) - self.assertDictEqual(separator_page_numbers, {}) - @override_settings( CONSUMER_ENABLE_BARCODES=True, CONSUMER_ENABLE_ASN_BARCODE=True, @@ -722,11 +523,64 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase): self.assertEqual(len(document_list), 5) -class TestAsnBarcode(DirectoriesMixin, TestCase): - SAMPLE_DIR = Path(__file__).parent / "samples" +@override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR") +class TestBarcodeNewConsume( + DirectoriesMixin, + FileSystemAssertsMixin, + SampleDirMixin, + DocumentConsumeDelayMixin, + TestCase, +): + @override_settings(CONSUMER_ENABLE_BARCODES=True) + def test_consume_barcode_file(self): + """ + GIVEN: + - Incoming file with at 1 barcode producing 2 documents + - Document includes metadata override information + WHEN: + - The document is split + THEN: + - Two new consume tasks are created + - Metadata overrides are preserved for the new consume + - The document source is unchanged (for consume templates) + """ + test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf" + temp_copy = self.dirs.scratch_dir / test_file.name + shutil.copy(test_file, temp_copy) - BARCODE_SAMPLE_DIR = SAMPLE_DIR / "barcodes" + overrides = DocumentMetadataOverrides(tag_ids=[1, 2, 9]) + with mock.patch("documents.tasks.async_to_sync") as progress_mocker: + self.assertEqual( + tasks.consume_file( + ConsumableDocument( + source=DocumentSource.ConsumeFolder, + original_file=temp_copy, + ), + overrides, + ), + "File successfully split", + ) + # We let the consumer know progress is done + progress_mocker.assert_called_once() + # 2 new document consume tasks created + self.assertEqual(self.consume_file_mock.call_count, 2) + + self.assertIsNotFile(temp_copy) + + # Check the split files exist + # Check the source is unchanged + # Check the overrides are unchanged + for ( + new_input_doc, + new_doc_overrides, + ) in self.get_all_consume_delay_call_args(): + self.assertEqual(new_input_doc.source, DocumentSource.ConsumeFolder) + self.assertIsFile(new_input_doc.original_file) + self.assertEqual(overrides, new_doc_overrides) + + +class TestAsnBarcode(DirectoriesMixin, SampleDirMixin, TestCase): @override_settings(CONSUMER_ASN_BARCODE_PREFIX="CUSTOM-PREFIX-") def test_scan_file_for_asn_custom_prefix(self): """ diff --git a/src/documents/tests/utils.py b/src/documents/tests/utils.py index b1e9c0523..fe7dbb059 100644 --- a/src/documents/tests/utils.py +++ b/src/documents/tests/utils.py @@ -235,8 +235,10 @@ class DocumentConsumeDelayMixin: """ Iterates over all calls to the async task and returns the arguments """ + # Must be at least 1 call + self.consume_file_mock.assert_called() - for args, _ in self.consume_file_mock.call_args_list: + for args, kwargs in self.consume_file_mock.call_args_list: input_doc, overrides = args yield (input_doc, overrides) @@ -244,7 +246,7 @@ class DocumentConsumeDelayMixin: def get_specific_consume_delay_call_args( self, index: int, - ) -> Iterator[tuple[ConsumableDocument, DocumentMetadataOverrides]]: + ) -> tuple[ConsumableDocument, DocumentMetadataOverrides]: """ Returns the arguments of a specific call to the async task """ @@ -299,3 +301,9 @@ class TestMigrations(TransactionTestCase): def setUpBeforeMigration(self, apps): pass + + +class SampleDirMixin: + SAMPLE_DIR = Path(__file__).parent / "samples" + + BARCODE_SAMPLE_DIR = SAMPLE_DIR / "barcodes" From 9d5b07537d2fd4e701103178b0f406edf30ef7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Bogda=C5=82?= Date: Fri, 15 Dec 2023 20:36:25 +0100 Subject: [PATCH 7/8] Reduce number of db queries (#4990) --- src/documents/views.py | 77 +++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/src/documents/views.py b/src/documents/views.py index 6533c22f3..e8c6db0de 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -182,10 +182,14 @@ class PassUserMixin(CreateModelMixin): class CorrespondentViewSet(ModelViewSet, PassUserMixin): model = Correspondent - queryset = Correspondent.objects.annotate( - document_count=Count("documents"), - last_correspondence=Max("documents__created"), - ).order_by(Lower("name")) + queryset = ( + Correspondent.objects.annotate( + document_count=Count("documents"), + last_correspondence=Max("documents__created"), + ) + .select_related("owner") + .order_by(Lower("name")) + ) serializer_class = CorrespondentSerializer pagination_class = StandardPagination @@ -208,8 +212,12 @@ class CorrespondentViewSet(ModelViewSet, PassUserMixin): class TagViewSet(ModelViewSet, PassUserMixin): model = Tag - queryset = Tag.objects.annotate(document_count=Count("documents")).order_by( - Lower("name"), + queryset = ( + Tag.objects.annotate(document_count=Count("documents")) + .select_related("owner") + .order_by( + Lower("name"), + ) ) def get_serializer_class(self, *args, **kwargs): @@ -232,9 +240,13 @@ class TagViewSet(ModelViewSet, PassUserMixin): class DocumentTypeViewSet(ModelViewSet, PassUserMixin): model = DocumentType - queryset = DocumentType.objects.annotate( - document_count=Count("documents"), - ).order_by(Lower("name")) + queryset = ( + DocumentType.objects.annotate( + document_count=Count("documents"), + ) + .select_related("owner") + .order_by(Lower("name")) + ) serializer_class = DocumentTypeSerializer pagination_class = StandardPagination @@ -283,7 +295,12 @@ class DocumentViewSet( ) def get_queryset(self): - return Document.objects.distinct().annotate(num_notes=Count("notes")) + return ( + Document.objects.distinct() + .annotate(num_notes=Count("notes")) + .select_related("correspondent", "storage_path", "document_type", "owner") + .prefetch_related("tags", "custom_fields", "notes") + ) def get_serializer(self, *args, **kwargs): fields_param = self.request.query_params.get("fields", None) @@ -627,9 +644,18 @@ class DocumentViewSet( class SearchResultSerializer(DocumentSerializer, PassUserMixin): def to_representation(self, instance): - doc = Document.objects.get(id=instance["id"]) + doc = ( + Document.objects.select_related( + "correspondent", + "storage_path", + "document_type", + "owner", + ) + .prefetch_related("tags", "custom_fields", "notes") + .get(id=instance["id"]) + ) notes = ",".join( - [str(c.note) for c in Note.objects.filter(document=instance["id"])], + [str(c.note) for c in doc.notes.all()], ) r = super().to_representation(doc) r["__search_hit__"] = { @@ -752,7 +778,11 @@ class SavedViewViewSet(ModelViewSet, PassUserMixin): def get_queryset(self): user = self.request.user - return SavedView.objects.filter(owner=user) + return ( + SavedView.objects.filter(owner=user) + .select_related("owner") + .prefetch_related("filter_rules") + ) def perform_create(self, serializer): serializer.save(owner=self.request.user) @@ -1080,8 +1110,12 @@ class BulkDownloadView(GenericAPIView): class StoragePathViewSet(ModelViewSet, PassUserMixin): model = StoragePath - queryset = StoragePath.objects.annotate(document_count=Count("documents")).order_by( - Lower("name"), + queryset = ( + StoragePath.objects.annotate(document_count=Count("documents")) + .select_related("owner") + .order_by( + Lower("name"), + ) ) serializer_class = StoragePathSerializer @@ -1347,7 +1381,18 @@ class ConsumptionTemplateViewSet(ModelViewSet): model = ConsumptionTemplate - queryset = ConsumptionTemplate.objects.all().order_by("order") + queryset = ( + ConsumptionTemplate.objects.prefetch_related( + "assign_tags", + "assign_view_users", + "assign_view_groups", + "assign_change_users", + "assign_change_groups", + "assign_custom_fields", + ) + .all() + .order_by("order") + ) class CustomFieldViewSet(ModelViewSet): From b1eced36123c65bd5fb838386e8472f858dd3605 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:03:38 -0800 Subject: [PATCH 8/8] New Crowdin translations by GitHub Action (#4967) Co-authored-by: Crowdin Bot --- src-ui/src/locale/messages.fr_FR.xlf | 8 +- src-ui/src/locale/messages.hr_HR.xlf | 20 +- src-ui/src/locale/messages.ro_RO.xlf | 270 ++++++++++++------------- src/locale/fr_FR/LC_MESSAGES/django.po | 2 +- src/locale/hr_HR/LC_MESSAGES/django.po | 2 +- src/locale/ro_RO/LC_MESSAGES/django.po | 100 ++++----- src/locale/zh_TW/LC_MESSAGES/django.po | 178 ++++++++-------- 7 files changed, 290 insertions(+), 290 deletions(-) diff --git a/src-ui/src/locale/messages.fr_FR.xlf b/src-ui/src/locale/messages.fr_FR.xlf index d8e7aea86..3f550be66 100644 --- a/src-ui/src/locale/messages.fr_FR.xlf +++ b/src-ui/src/locale/messages.fr_FR.xlf @@ -10,13 +10,13 @@ Fermer - + HH node_modules/src/ngb-config.ts 13 - HH + HH Close @@ -100,13 +100,13 @@ Précédent - + MM node_modules/src/ngb-config.ts 13 - MM + MM » diff --git a/src-ui/src/locale/messages.hr_HR.xlf b/src-ui/src/locale/messages.hr_HR.xlf index 7f3b2061c..fbc08b75c 100644 --- a/src-ui/src/locale/messages.hr_HR.xlf +++ b/src-ui/src/locale/messages.hr_HR.xlf @@ -288,7 +288,7 @@ src/app/app.component.ts 90 - Document was added to Paperless-ngx. + Dokument je dodan u Paperless-ngx. Open document @@ -316,7 +316,7 @@ src/app/app.component.ts 120 - Document is being processed by Paperless-ngx. + Dokument je u fazi obrade. Prev @@ -476,7 +476,7 @@ src/app/components/admin/tasks/tasks.component.html 15 - Auto refresh + Automatsko osvježavanje Loading... @@ -596,7 +596,7 @@ src/app/components/admin/settings/settings.component.html 15 - General + Općenito Appearance @@ -916,7 +916,7 @@ src/app/components/admin/settings/settings.component.html 178,180 - Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI + Postavke ovog korisničkog računa za objekte (Oznake, Pravila za e-poštu, itd.) stvorene putem web sučelja Default Owner @@ -2074,7 +2074,7 @@ src/app/components/admin/users-groups/users-groups.component.ts 124 - Deleted user + Izbrisani korisnik Error deleting user. @@ -2479,7 +2479,7 @@ src/app/components/app-frame/app-frame.component.ts 276 - An error occurred while saving update checking settings. + Došlo je do pogreške prilikom spremanja postavki ažuriranja. Clear @@ -3039,7 +3039,7 @@ src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html 9 - Data type + Tip podataka Data type cannot be changed after a field is created @@ -3047,7 +3047,7 @@ src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html 10 - Data type cannot be changed after a field is created + Tip podataka ne može se promijeniti nakon što je polje stvoreno Create new custom field @@ -3175,7 +3175,7 @@ src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html 19 - Character Set + Skup znakova Test diff --git a/src-ui/src/locale/messages.ro_RO.xlf b/src-ui/src/locale/messages.ro_RO.xlf index 2e032d5ea..caa977f7a 100644 --- a/src-ui/src/locale/messages.ro_RO.xlf +++ b/src-ui/src/locale/messages.ro_RO.xlf @@ -16,7 +16,7 @@ node_modules/src/ngb-config.ts 13 - HH + HH Close @@ -44,7 +44,7 @@ node_modules/src/ngb-config.ts 13 - Select month + Selectați luna Previous month @@ -56,7 +56,7 @@ node_modules/src/ngb-config.ts 13 - Previous month + Luna precedentă @@ -82,7 +82,7 @@ node_modules/src/ngb-config.ts 13 - Hours + Ore « @@ -98,7 +98,7 @@ node_modules/src/ngb-config.ts 13 - Previous + Anterior MM @@ -106,7 +106,7 @@ node_modules/src/ngb-config.ts 13 - MM + MM » @@ -126,7 +126,7 @@ node_modules/src/ngb-config.ts 13 - Select year + Selectați anul Next month @@ -138,7 +138,7 @@ node_modules/src/ngb-config.ts 13 - Next month + Luna următoare Next @@ -146,7 +146,7 @@ node_modules/src/ngb-config.ts 13 - Next + Următor Minutes @@ -154,7 +154,7 @@ node_modules/src/ngb-config.ts 13 - Minutes + Minute »» @@ -170,7 +170,7 @@ node_modules/src/ngb-config.ts 13 - Increment hours + Incrementare ore First @@ -178,7 +178,7 @@ node_modules/src/ngb-config.ts 13 - First + Primul Previous @@ -186,7 +186,7 @@ node_modules/src/ngb-config.ts 13 - Previous + Anteriorul Decrement hours @@ -194,7 +194,7 @@ node_modules/src/ngb-config.ts 13 - Decrement hours + Decrementare oră Next @@ -202,7 +202,7 @@ node_modules/src/ngb-config.ts 13 - Next + Următor Increment minutes @@ -210,7 +210,7 @@ node_modules/src/ngb-config.ts 13 - Increment minutes + Incrementare minute Last @@ -218,7 +218,7 @@ node_modules/src/ngb-config.ts 13 - Last + Ultima Decrement minutes @@ -226,7 +226,7 @@ node_modules/src/ngb-config.ts 13 - Decrement minutes + Decrementare minute SS @@ -234,7 +234,7 @@ node_modules/src/ngb-config.ts 13 - SS + SS Seconds @@ -242,7 +242,7 @@ node_modules/src/ngb-config.ts 13 - Seconds + Secunde Increment seconds @@ -250,7 +250,7 @@ node_modules/src/ngb-config.ts 13 - Increment seconds + Incrementare secunde Decrement seconds @@ -258,7 +258,7 @@ node_modules/src/ngb-config.ts 13 - Decrement seconds + Decrementare secunde @@ -288,7 +288,7 @@ src/app/app.component.ts 90 - Document was added to Paperless-ngx. + Documentul a fost adăugat în sistem. Open document @@ -316,7 +316,7 @@ src/app/app.component.ts 120 - Document is being processed by Paperless-ngx. + Documentul este procesat de către sistem. Prev @@ -324,7 +324,7 @@ src/app/app.component.ts 126 - Prev + Anterior Next @@ -336,7 +336,7 @@ src/app/components/document-detail/document-detail.component.html 92 - Next + Următor End @@ -344,7 +344,7 @@ src/app/app.component.ts 128 - End + Sfârșit The dashboard can be used to show saved views, such as an 'Inbox'. Those settings are found under Settings > Saved Views once you have created some. @@ -352,7 +352,7 @@ src/app/app.component.ts 134 - The dashboard can be used to show saved views, such as an 'Inbox'. Those settings are found under Settings > Saved Views once you have created some. + Tabloul de bord poate fi folosit pentru a afișa vizualizările salvate, cum ar fi un 'Inbox'. Aceste setări sunt găsite în Setările > Vizualizări salvate după ce aţi creat unele. Drag-and-drop documents here to start uploading or place them in the consume folder. You can also drag-and-drop documents anywhere on all other pages of the web app. Once you do, Paperless-ngx will start training its machine learning algorithms. @@ -360,7 +360,7 @@ src/app/app.component.ts 141 - Drag-and-drop documents here to start uploading or place them in the consume folder. You can also drag-and-drop documents anywhere on all other pages of the web app. Once you do, Paperless-ngx will start training its machine learning algorithms. + Drag-and-drop documente aici pentru a începe încărcarea sau plasarea lor în folderul de consum. De asemenea, puteți trage și plasa documente oriunde pe toate celelalte pagini ale aplicației web. Odată ce faceți acest lucru, sistemul va începe să își instruiască algoritmii de învățare a mașinilor. The documents list shows all of your documents and allows for filtering as well as bulk-editing. There are three different view styles: list, small cards and large cards. A list of documents currently opened for editing is shown in the sidebar. @@ -368,7 +368,7 @@ src/app/app.component.ts 146 - The documents list shows all of your documents and allows for filtering as well as bulk-editing. There are three different view styles: list, small cards and large cards. A list of documents currently opened for editing is shown in the sidebar. + Lista de documente afișează toate documentele și permite atât filtrarea, cât și editarea în grup. Există trei stiluri de vizualizare diferite: listă, carduri mici și carduri mari. O listă de documente deschise în prezent pentru editare este afișată în bara laterală. The filtering tools allow you to quickly find documents using various searches, dates, tags, etc. @@ -376,7 +376,7 @@ src/app/app.component.ts 153 - The filtering tools allow you to quickly find documents using various searches, dates, tags, etc. + Instrumentele de filtrare vă permit să găsiți rapid documente folosind diverse căutări, date, etichete, etc. Any combination of filters can be saved as a 'view' which can then be displayed on the dashboard and / or sidebar. @@ -384,7 +384,7 @@ src/app/app.component.ts 159 - Any combination of filters can be saved as a 'view' which can then be displayed on the dashboard and / or sidebar. + Orice combinație de filtre poate fi salvată ca o 'vizualizare' care poate fi afișată apoi pe tabloul de bord și/sau pe bara laterală. Tags, correspondents, document types and storage paths can all be managed using these pages. They can also be created from the document edit view. @@ -392,7 +392,7 @@ src/app/app.component.ts 164 - Tags, correspondents, document types and storage paths can all be managed using these pages. They can also be created from the document edit view. + Tag-uri, corespondenți, tipuri de documente și căi de stocare pot fi gestionate toate folosind aceste pagini. De asemenea, pot fi create din editarea documentului. Manage e-mail accounts and rules for automatically importing documents. @@ -400,7 +400,7 @@ src/app/app.component.ts 172 - Manage e-mail accounts and rules for automatically importing documents. + Gestionați conturile de e-mail și regulile pentru importul automat de documente. Consumption templates give you finer control over the document ingestion process. @@ -408,7 +408,7 @@ src/app/app.component.ts 180 - Consumption templates give you finer control over the document ingestion process. + Şabloanele de consum vă oferă un control mai bun asupra procesului de ingestie a documentului. File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process. @@ -416,7 +416,7 @@ src/app/app.component.ts 188 - File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process. + Sarcinile fișierului vă arată documentele care au fost consumate, care așteaptă să fie, sau este posibil să fi eșuat în timpul procesului. Check out the settings for various tweaks to the web app and toggle settings for saved views. @@ -424,7 +424,7 @@ src/app/app.component.ts 196 - Check out the settings for various tweaks to the web app and toggle settings for saved views. + Verificați setările pentru diferite modificări ale aplicației web și comutați setările pentru vizualizările salvate. Thank you! 🙏 @@ -432,7 +432,7 @@ src/app/app.component.ts 204 - Thank you! 🙏 + Mulțumesc! :plided_hands: There are <em>tons</em> more features and info we didn't cover here, but this should get you started. Check out the documentation or visit the project on GitHub to learn more or to report issues. @@ -448,7 +448,7 @@ src/app/app.component.ts 208 - Lastly, on behalf of every contributor to this community-supported project, thank you for using Paperless-ngx! + În cele din urmă, în numele fiecărui colaborator la acest proiect sprijinit de comunitate, vă mulţumim pentru că ați folosit Paperless-ngx! Logs @@ -476,7 +476,7 @@ src/app/components/admin/tasks/tasks.component.html 15 - Auto refresh + Actualizare automată Loading... @@ -580,7 +580,7 @@ src/app/components/admin/settings/settings.component.html 2 - Start tour + Începeți turul Open Django Admin @@ -588,7 +588,7 @@ src/app/components/admin/settings/settings.component.html 4 - Open Django Admin + Deschideți Administratorul Django General @@ -596,7 +596,7 @@ src/app/components/admin/settings/settings.component.html 15 - General + Setări generale Appearance @@ -700,7 +700,7 @@ src/app/components/admin/settings/settings.component.html 99 - Sidebar + Bară laterală Use 'slim' sidebar (icons only) @@ -708,7 +708,7 @@ src/app/components/admin/settings/settings.component.html 103 - Use 'slim' sidebar (icons only) + Utilizaţi bara laterală "subţire" (numai pictograme) Dark mode @@ -748,7 +748,7 @@ src/app/components/admin/settings/settings.component.html 121 - Theme Color + Culoarea temei Reset @@ -756,7 +756,7 @@ src/app/components/admin/settings/settings.component.html 130 - Reset + Inițializare Update checking @@ -764,7 +764,7 @@ src/app/components/admin/settings/settings.component.html 135 - Update checking + Verificare actualizare Update checking works by pinging the public GitHub API for the latest release to determine whether a new version is available. Actual updating of the app must still be performed manually. @@ -1946,7 +1946,7 @@ src/app/components/admin/users-groups/users-groups.component.html 89 - No groups defined + Nu există grupuri definite Password has been changed, you will be logged out momentarily. @@ -2163,7 +2163,7 @@ src/app/components/app-frame/app-frame.component.html 45 - My Profile + Profilul meu Logout @@ -2327,7 +2327,7 @@ src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html 61 - Document Types + Tipuri de documente Storage Paths @@ -2343,7 +2343,7 @@ src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html 67 - Storage Paths + Căi de stocare Custom Fields @@ -2363,7 +2363,7 @@ src/app/components/manage/custom-fields/custom-fields.component.html 1 - Custom Fields + Câmpuri personalizate Consumption templates @@ -2371,7 +2371,7 @@ src/app/components/app-frame/app-frame.component.html 188 - Consumption templates + Șabloane de consum Templates @@ -2379,7 +2379,7 @@ src/app/components/app-frame/app-frame.component.html 191 - Templates + Șabloane Mail @@ -2391,7 +2391,7 @@ src/app/components/app-frame/app-frame.component.html 198 - Mail + Mail Administration @@ -2399,7 +2399,7 @@ src/app/components/app-frame/app-frame.component.html 204 - Administration + Administrare File Tasks @@ -2423,7 +2423,7 @@ src/app/components/app-frame/app-frame.component.html 252 - is available. + este disponibilă. Click to view. @@ -2431,7 +2431,7 @@ src/app/components/app-frame/app-frame.component.html 252 - Click to view. + Click pentru a vizualiza. Paperless-ngx can automatically check for updates @@ -2439,7 +2439,7 @@ src/app/components/app-frame/app-frame.component.html 256 - Paperless-ngx can automatically check for updates + Sistemul poate verifica automat actualizările How does this work? @@ -2447,7 +2447,7 @@ src/app/components/app-frame/app-frame.component.html 263,265 - How does this work? + Cum funcţionează? Update available @@ -2455,7 +2455,7 @@ src/app/components/app-frame/app-frame.component.html 274 - Update available + Actualizare disponibilă Sidebar views updated @@ -2463,7 +2463,7 @@ src/app/components/app-frame/app-frame.component.ts 252 - Sidebar views updated + Vizualizări bară laterală actualizate Error updating sidebar views @@ -2471,7 +2471,7 @@ src/app/components/app-frame/app-frame.component.ts 255 - Error updating sidebar views + Eroare la actualizarea vizualizărilor barei laterale An error occurred while saving update checking settings. @@ -2547,7 +2547,7 @@ src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.html 25 - Create New Field + Creare câmp nou Add @@ -2559,7 +2559,7 @@ src/app/components/common/permissions-select/permissions-select.component.html 7 - Add + Adaugă Choose field @@ -2567,7 +2567,7 @@ src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.ts 52 - Choose field + Selectare câmp No unused fields found @@ -2599,7 +2599,7 @@ src/app/components/manage/custom-fields/custom-fields.component.ts 66 - Error saving field. + Eroare la salvarea câmpului. now @@ -2607,7 +2607,7 @@ src/app/components/common/date-dropdown/date-dropdown.component.html 20 - now + acum After @@ -2667,7 +2667,7 @@ src/app/components/manage/consumption-templates/consumption-templates.component.html 15 - Sort order + Ordinea de sortare Filters @@ -2675,7 +2675,7 @@ src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html 18 - Filters + Filtre Process documents that match all filters specified below. @@ -2691,7 +2691,7 @@ src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html 20 - Filter sources + Filtrare surse Filter filename @@ -2699,7 +2699,7 @@ src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html 21 - Filter filename + Filtru nume fișier Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive. @@ -2707,7 +2707,7 @@ src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html 21 - Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive. + Aplică la documentele care se potrivesc cu acest nume de fișier. Wildcards cum ar fi *.pdf sau *factura* sunt permise. Masca insensibilă. Filter path @@ -2715,7 +2715,7 @@ src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html 22 - Filter path + Filtrare cale Apply to documents that match this path. Wildcards specified as * are allowed. Case insensitive.</a> @@ -2731,7 +2731,7 @@ src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html 23 - Filter mail rule + Regulă filtrare e-mail Apply to documents consumed via this mail rule. @@ -2739,7 +2739,7 @@ src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html 23 - Apply to documents consumed via this mail rule. + Aplică la documentele consumate prin această regulă de e-mail. Assignments @@ -3199,7 +3199,7 @@ src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts 15 - SSL + SSL STARTTLS @@ -3207,7 +3207,7 @@ src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts 16 - STARTTLS + STARTTLS Create new mail account @@ -3215,7 +3215,7 @@ src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts 41 - Create new mail account + Creează un cont de mail nou Edit mail account @@ -3223,7 +3223,7 @@ src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts 45 - Edit mail account + Editare cont de mail Successfully connected to the mail server @@ -3231,7 +3231,7 @@ src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts 90 - Successfully connected to the mail server + Conectat cu succes la serverul de e-mail Unable to connect to the mail server @@ -3239,7 +3239,7 @@ src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts 91 - Unable to connect to the mail server + Problemă la conectarea la server Account @@ -3251,7 +3251,7 @@ src/app/components/manage/mail/mail.component.html 67 - Account + Cont Folder @@ -3259,7 +3259,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 12 - Folder + Dosar Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server. @@ -3267,7 +3267,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 12 - Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server. + Subdosarele trebuie separate printr-un delimitator, adesea un punct ('.') sau slash ('/'), dar variază de la serverul de mail. Maximum age (days) @@ -3275,7 +3275,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 13 - Maximum age (days) + Vârsta maximă (zile) Attachment type @@ -3283,7 +3283,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 14 - Attachment type + Tip atașament Consumption scope @@ -3291,7 +3291,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 15 - Consumption scope + Sfera de consum See docs for .eml processing requirements @@ -3323,7 +3323,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 20 - Filter from + Filtrează de la Filter to @@ -3331,7 +3331,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 21 - Filter to + Filtrează către Filter subject @@ -3339,7 +3339,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 22 - Filter subject + Filtrează subiect Filter body @@ -3347,7 +3347,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 23 - Filter body + Filtrează corpul email-ului Filter attachment filename includes @@ -3355,7 +3355,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 24 - Filter attachment filename includes + Filtrul de nume al fișierului include Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive. @@ -3371,7 +3371,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 25 - Filter attachment filename excluding + Filtru nume fișier atașat exclusiv Do not consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive. @@ -3387,7 +3387,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 28 - Action + Acţiune Action is only performed when documents are consumed from the mail. Mails without attachments remain entirely untouched. @@ -3395,7 +3395,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 28 - Action is only performed when documents are consumed from the mail. Mails without attachments remain entirely untouched. + Acțiunea este efectuată numai atunci când documentele sunt consumate din e-mail. Mailurile fără atașamente rămân în întregime neatinse. Action parameter @@ -3403,7 +3403,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 29 - Action parameter + Parametru acțiune Assignments specified here will supersede any consumption templates. @@ -3411,7 +3411,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 30 - Assignments specified here will supersede any consumption templates. + Atribuirile specificate aici vor înlocui orice șabloane de consum. Assign title from @@ -3419,7 +3419,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 31 - Assign title from + Atribuie titlu din Assign correspondent from @@ -3427,7 +3427,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 34 - Assign correspondent from + Atribuie corespondent din Assign owner from rule @@ -3435,7 +3435,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 36 - Assign owner from rule + Atribuiți proprietarul din regulă Only process attachments @@ -3567,7 +3567,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts 145 - Create new mail rule + Creați o nouă regulă pentru mail Edit mail rule @@ -3575,7 +3575,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts 149 - Edit mail rule + Editați regula pentru e-mail Path @@ -3587,7 +3587,7 @@ src/app/components/manage/storage-path-list/storage-path-list.component.ts 42 - Path + Cale e.g. @@ -3595,7 +3595,7 @@ src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts 28 - e.g. + de ex. or use slashes to add directories e.g. @@ -3603,7 +3603,7 @@ src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts 30 - or use slashes to add directories e.g. + sau utilizaţi slash-uri pentru a adăuga directoare, de ex. See <a target="_blank" href="https://docs.paperless-ngx.com/advanced_usage/#file-name-handling">documentation</a> for full list. @@ -3619,7 +3619,7 @@ src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts 37 - Create new storage path + Creează o nouă cale de stocare Edit storage path @@ -3627,7 +3627,7 @@ src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts 41 - Edit storage path + Editează calea de stocare Color @@ -3683,7 +3683,7 @@ src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html 8 - Email + Poștă electronică First name @@ -3695,7 +3695,7 @@ src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html 28 - First name + Prenume Last name @@ -3707,7 +3707,7 @@ src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html 29 - Last name + Nume de familie Active @@ -3715,7 +3715,7 @@ src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html 19 - Active + Activ Superuser @@ -3723,7 +3723,7 @@ src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html 23 - Superuser + Super utilizator (Grants all permissions and can view objects) @@ -3731,7 +3731,7 @@ src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html 23 - (Grants all permissions and can view objects) + (Alocă toate permisiunile si pot vizualiza obiectele) Create new user account @@ -3739,7 +3739,7 @@ src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts 44 - Create new user account + Crează un nou cont de utilizator Edit user account @@ -3747,7 +3747,7 @@ src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts 48 - Edit user account + Editează contul de utilizator All @@ -4261,7 +4261,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.html 46 - Expires + Expiră Create @@ -4281,7 +4281,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts 97 - 1 day + 1 zi 7 days @@ -4289,7 +4289,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts 20 - 7 days + 7 zile 30 days @@ -4297,7 +4297,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts 21 - 30 days + 30 zile Never @@ -4305,7 +4305,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts 22 - Never + Niciodată Error retrieving links @@ -4313,7 +4313,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts 78 - Error retrieving links + Eroare la preluarea link-urilor days @@ -4321,7 +4321,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts 97 - days + Zile Error deleting link @@ -4329,7 +4329,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts 126 - Error deleting link + Eroare la ștergerea link-ului Error creating link @@ -4337,7 +4337,7 @@ src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts 156 - Error creating link + Eroare la crearea linkului Status @@ -4345,7 +4345,7 @@ src/app/components/common/toasts/toasts.component.html 22 - Status + Stare Copy Raw Error @@ -4353,7 +4353,7 @@ src/app/components/common/toasts/toasts.component.html 33 - Copy Raw Error + Eroare de copiere brută Hello , welcome to Paperless-ngx @@ -4377,7 +4377,7 @@ src/app/components/dashboard/dashboard.component.ts 71 - Dashboard updated + Tablou de bord actualizat Error updating dashboard @@ -4385,7 +4385,7 @@ src/app/components/dashboard/dashboard.component.ts 74 - Error updating dashboard + Eroare la actualizarea tabloului de bord Show all @@ -4457,7 +4457,7 @@ src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html 31 - View Preview + Vezi previzualizarea Download @@ -4489,7 +4489,7 @@ src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html 53 - No documents + Nu aveți documente Statistics @@ -4505,7 +4505,7 @@ src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html 4 - Go to inbox + Salt la primite Documents in inbox @@ -4513,7 +4513,7 @@ src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html 5 - Documents in inbox + Documente în mesaje primite Go to documents @@ -4521,7 +4521,7 @@ src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html 8 - Go to documents + Mergeți la documente Total documents diff --git a/src/locale/fr_FR/LC_MESSAGES/django.po b/src/locale/fr_FR/LC_MESSAGES/django.po index 2cbfe6617..b6b2f9ca0 100644 --- a/src/locale/fr_FR/LC_MESSAGES/django.po +++ b/src/locale/fr_FR/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-12-05 08:26-0800\n" -"PO-Revision-Date: 2023-12-12 00:24\n" +"PO-Revision-Date: 2023-12-14 00:23\n" "Last-Translator: \n" "Language-Team: French\n" "Language: fr_FR\n" diff --git a/src/locale/hr_HR/LC_MESSAGES/django.po b/src/locale/hr_HR/LC_MESSAGES/django.po index 8a6450d04..2238b348e 100644 --- a/src/locale/hr_HR/LC_MESSAGES/django.po +++ b/src/locale/hr_HR/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-12-05 08:26-0800\n" -"PO-Revision-Date: 2023-12-05 16:27\n" +"PO-Revision-Date: 2023-12-13 12:09\n" "Last-Translator: \n" "Language-Team: Croatian\n" "Language: hr_HR\n" diff --git a/src/locale/ro_RO/LC_MESSAGES/django.po b/src/locale/ro_RO/LC_MESSAGES/django.po index 68b54c0c1..dc1f52e32 100644 --- a/src/locale/ro_RO/LC_MESSAGES/django.po +++ b/src/locale/ro_RO/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-12-05 08:26-0800\n" -"PO-Revision-Date: 2023-12-05 16:27\n" +"PO-Revision-Date: 2023-12-16 00:23\n" "Last-Translator: \n" "Language-Team: Romanian\n" "Language: ro_RO\n" @@ -23,11 +23,11 @@ msgstr "Documente" #: documents/models.py:36 documents/models.py:734 msgid "owner" -msgstr "" +msgstr "proprietar" #: documents/models.py:53 msgid "None" -msgstr "" +msgstr "Nimic" #: documents/models.py:54 msgid "Any word" @@ -108,15 +108,15 @@ msgstr "tipuri de document" #: documents/models.py:124 msgid "path" -msgstr "" +msgstr "cale" #: documents/models.py:129 documents/models.py:156 msgid "storage path" -msgstr "" +msgstr "cale de stocare" #: documents/models.py:130 msgid "storage paths" -msgstr "" +msgstr "căi de stocare" #: documents/models.py:137 msgid "Unencrypted" @@ -193,11 +193,11 @@ msgstr "Numele curent al arhivei stocate" #: documents/models.py:250 msgid "original filename" -msgstr "" +msgstr "numele original al fișierului" #: documents/models.py:256 msgid "The original name of the file when it was uploaded" -msgstr "" +msgstr "Numele original al fișierului când a fost încărcat" #: documents/models.py:263 msgid "archive serial number" @@ -381,47 +381,47 @@ msgstr "" #: documents/models.py:447 msgid "storage path is" -msgstr "" +msgstr "calea de stocare este" #: documents/models.py:448 msgid "has correspondent in" -msgstr "" +msgstr "are corespondent în" #: documents/models.py:449 msgid "does not have correspondent in" -msgstr "" +msgstr "nu are corespondent în" #: documents/models.py:450 msgid "has document type in" -msgstr "" +msgstr "are tip de document în" #: documents/models.py:451 msgid "does not have document type in" -msgstr "" +msgstr "nu are tip document în" #: documents/models.py:452 msgid "has storage path in" -msgstr "" +msgstr "are cale de stocare în" #: documents/models.py:453 msgid "does not have storage path in" -msgstr "" +msgstr "nu are cale de stocare în" #: documents/models.py:454 msgid "owner is" -msgstr "" +msgstr "proprietarul este" #: documents/models.py:455 msgid "has owner in" -msgstr "" +msgstr "are proprietar în" #: documents/models.py:456 msgid "does not have owner" -msgstr "" +msgstr "nu are proprietar" #: documents/models.py:457 msgid "does not have owner in" -msgstr "" +msgstr "nu are proprietar în" #: documents/models.py:467 msgid "rule type" @@ -441,47 +441,47 @@ msgstr "reguli de filtrare" #: documents/models.py:584 msgid "Task ID" -msgstr "" +msgstr "ID Sarcină" #: documents/models.py:585 msgid "Celery ID for the Task that was run" -msgstr "" +msgstr "ID-ul sarcinii Celery care a fost rulată" #: documents/models.py:590 msgid "Acknowledged" -msgstr "" +msgstr "Confirmat" #: documents/models.py:591 msgid "If the task is acknowledged via the frontend or API" -msgstr "" +msgstr "Dacă sarcina este confirmată prin frontend sau API" #: documents/models.py:597 msgid "Task Filename" -msgstr "" +msgstr "Numele fișierului sarcină" #: documents/models.py:598 msgid "Name of the file which the Task was run for" -msgstr "" +msgstr "Numele fișierului pentru care sarcina a fost executată" #: documents/models.py:604 msgid "Task Name" -msgstr "" +msgstr "Nume sarcină" #: documents/models.py:605 msgid "Name of the Task which was run" -msgstr "" +msgstr "Numele sarcinii care a fost executată" #: documents/models.py:612 msgid "Task State" -msgstr "" +msgstr "Stare sarcină" #: documents/models.py:613 msgid "Current state of the task being run" -msgstr "" +msgstr "Stadiul actual al sarcinii în curs de desfășurare" #: documents/models.py:618 msgid "Created DateTime" -msgstr "" +msgstr "Data creării" #: documents/models.py:619 msgid "Datetime field when the task result was created in UTC" @@ -489,7 +489,7 @@ msgstr "" #: documents/models.py:624 msgid "Started DateTime" -msgstr "" +msgstr "Data începerii" #: documents/models.py:625 msgid "Datetime field when the task was started in UTC" @@ -497,7 +497,7 @@ msgstr "" #: documents/models.py:630 msgid "Completed DateTime" -msgstr "" +msgstr "Data finalizării" #: documents/models.py:631 msgid "Datetime field when the task was completed in UTC" @@ -505,15 +505,15 @@ msgstr "" #: documents/models.py:636 msgid "Result Data" -msgstr "" +msgstr "Datele rezultatului" #: documents/models.py:638 msgid "The data returned by the task" -msgstr "" +msgstr "Datele returnate de sarcină" #: documents/models.py:650 msgid "Note for the document" -msgstr "" +msgstr "Notă pentru document" #: documents/models.py:674 msgid "user" @@ -521,23 +521,23 @@ msgstr "utilizator" #: documents/models.py:679 msgid "note" -msgstr "" +msgstr "notă" #: documents/models.py:680 msgid "notes" -msgstr "" +msgstr "note" #: documents/models.py:688 msgid "Archive" -msgstr "" +msgstr "Arhivă" #: documents/models.py:689 msgid "Original" -msgstr "" +msgstr "Original" #: documents/models.py:700 msgid "expiration" -msgstr "" +msgstr "expirare" #: documents/models.py:707 msgid "slug" @@ -545,35 +545,35 @@ msgstr "" #: documents/models.py:739 msgid "share link" -msgstr "" +msgstr "link de partajare" #: documents/models.py:740 msgid "share links" -msgstr "" +msgstr "link-uri de partajare" #: documents/models.py:752 msgid "String" -msgstr "" +msgstr "Şir de caractere" #: documents/models.py:753 msgid "URL" -msgstr "" +msgstr "Adresă URL" #: documents/models.py:754 msgid "Date" -msgstr "" +msgstr "Dată" #: documents/models.py:755 msgid "Boolean" -msgstr "" +msgstr "Boolean" #: documents/models.py:756 msgid "Integer" -msgstr "" +msgstr "Număr întreg" #: documents/models.py:757 msgid "Float" -msgstr "" +msgstr "Număr zecimal" #: documents/models.py:758 msgid "Monetary" @@ -581,11 +581,11 @@ msgstr "" #: documents/models.py:759 msgid "Document Link" -msgstr "" +msgstr "Link document" #: documents/models.py:771 msgid "data type" -msgstr "" +msgstr "tip date" #: documents/models.py:779 msgid "custom field" diff --git a/src/locale/zh_TW/LC_MESSAGES/django.po b/src/locale/zh_TW/LC_MESSAGES/django.po index e11c8ed0d..ab28d2394 100644 --- a/src/locale/zh_TW/LC_MESSAGES/django.po +++ b/src/locale/zh_TW/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-12-05 08:26-0800\n" -"PO-Revision-Date: 2023-12-05 16:27\n" +"PO-Revision-Date: 2023-12-14 00:23\n" "Last-Translator: \n" "Language-Team: Chinese Traditional\n" "Language: zh_TW\n" @@ -47,7 +47,7 @@ msgstr "" #: documents/models.py:58 msgid "Fuzzy word" -msgstr "" +msgstr "模糊詞" #: documents/models.py:59 msgid "Automatic" @@ -68,15 +68,15 @@ msgstr "比對演算法" #: documents/models.py:72 msgid "is insensitive" -msgstr "" +msgstr "不區分大小寫" #: documents/models.py:95 documents/models.py:147 msgid "correspondent" -msgstr "" +msgstr "聯繫者" #: documents/models.py:96 msgid "correspondents" -msgstr "" +msgstr "聯繫者" #: documents/models.py:100 msgid "color" @@ -84,47 +84,47 @@ msgstr "顏色" #: documents/models.py:103 msgid "is inbox tag" -msgstr "" +msgstr "收件匣標籤" #: documents/models.py:106 msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags." -msgstr "" +msgstr "標記此標籤為收件匣標籤:所有新處理的文件將會以此收件匣標籤作標記。" #: documents/models.py:112 msgid "tag" -msgstr "" +msgstr "標籤" #: documents/models.py:113 documents/models.py:185 msgid "tags" -msgstr "" +msgstr "標籤" #: documents/models.py:118 documents/models.py:167 msgid "document type" -msgstr "" +msgstr "文件類型" #: documents/models.py:119 msgid "document types" -msgstr "" +msgstr "文件類型" #: documents/models.py:124 msgid "path" -msgstr "" +msgstr "位址" #: documents/models.py:129 documents/models.py:156 msgid "storage path" -msgstr "" +msgstr "儲存位址" #: documents/models.py:130 msgid "storage paths" -msgstr "" +msgstr "儲存位址" #: documents/models.py:137 msgid "Unencrypted" -msgstr "" +msgstr "未加密" #: documents/models.py:138 msgid "Encrypted with GNU Privacy Guard" -msgstr "" +msgstr "已使用 GNU Privacy Guard 進行加密" #: documents/models.py:159 msgid "title" @@ -189,27 +189,27 @@ msgstr "存檔檔案名稱" #: documents/models.py:246 msgid "Current archive filename in storage" -msgstr "" +msgstr "現時儲存空間封存的檔案名稱" #: documents/models.py:250 msgid "original filename" -msgstr "" +msgstr "原先檔案名稱" #: documents/models.py:256 msgid "The original name of the file when it was uploaded" -msgstr "" +msgstr "檔案上傳時的檔案名稱" #: documents/models.py:263 msgid "archive serial number" -msgstr "" +msgstr "封存編號" #: documents/models.py:273 msgid "The position of this document in your physical document archive." -msgstr "" +msgstr "此檔案在你實體儲存空間的位置。" #: documents/models.py:279 documents/models.py:665 documents/models.py:719 msgid "document" -msgstr "" +msgstr "文件" #: documents/models.py:280 msgid "documents" @@ -217,47 +217,47 @@ msgstr "文件" #: documents/models.py:368 msgid "debug" -msgstr "" +msgstr "偵錯" #: documents/models.py:369 msgid "information" -msgstr "" +msgstr "資訊" #: documents/models.py:370 msgid "warning" -msgstr "" +msgstr "警告" #: documents/models.py:371 paperless_mail/models.py:305 msgid "error" -msgstr "" +msgstr "錯誤" #: documents/models.py:372 msgid "critical" -msgstr "" +msgstr "嚴重" #: documents/models.py:375 msgid "group" -msgstr "" +msgstr "群組" #: documents/models.py:377 msgid "message" -msgstr "" +msgstr "訊息" #: documents/models.py:380 msgid "level" -msgstr "" +msgstr "程度" #: documents/models.py:389 msgid "log" -msgstr "" +msgstr "記錄" #: documents/models.py:390 msgid "logs" -msgstr "" +msgstr "記錄" #: documents/models.py:399 documents/models.py:464 msgid "saved view" -msgstr "" +msgstr "已儲存的檢視表" #: documents/models.py:400 msgid "saved views" @@ -265,207 +265,207 @@ msgstr "保存視圖" #: documents/models.py:405 msgid "show on dashboard" -msgstr "" +msgstr "顯示在概覽" #: documents/models.py:408 msgid "show in sidebar" -msgstr "" +msgstr "顯示在側邊欄" #: documents/models.py:412 msgid "sort field" -msgstr "" +msgstr "排序欄位" #: documents/models.py:417 msgid "sort reverse" -msgstr "" +msgstr "倒轉排序" #: documents/models.py:422 msgid "title contains" -msgstr "" +msgstr "標題包含" #: documents/models.py:423 msgid "content contains" -msgstr "" +msgstr "內容包含" #: documents/models.py:424 msgid "ASN is" -msgstr "" +msgstr "ASN 為" #: documents/models.py:425 msgid "correspondent is" -msgstr "" +msgstr "聯繫者為" #: documents/models.py:426 msgid "document type is" -msgstr "" +msgstr "文件類型為" #: documents/models.py:427 msgid "is in inbox" -msgstr "" +msgstr "在收件匣內" #: documents/models.py:428 msgid "has tag" -msgstr "" +msgstr "包含標籤" #: documents/models.py:429 msgid "has any tag" -msgstr "" +msgstr "包含任何標籤" #: documents/models.py:430 msgid "created before" -msgstr "" +msgstr "建立時間之前" #: documents/models.py:431 msgid "created after" -msgstr "" +msgstr "建立時間之後" #: documents/models.py:432 msgid "created year is" -msgstr "" +msgstr "建立年份為" #: documents/models.py:433 msgid "created month is" -msgstr "" +msgstr "建立月份為" #: documents/models.py:434 msgid "created day is" -msgstr "" +msgstr "建立日期為" #: documents/models.py:435 msgid "added before" -msgstr "" +msgstr "加入時間之前" #: documents/models.py:436 msgid "added after" -msgstr "" +msgstr "加入時間之後" #: documents/models.py:437 msgid "modified before" -msgstr "" +msgstr "修改之前" #: documents/models.py:438 msgid "modified after" -msgstr "" +msgstr "修改之後" #: documents/models.py:439 msgid "does not have tag" -msgstr "" +msgstr "沒有包含標籤" #: documents/models.py:440 msgid "does not have ASN" -msgstr "" +msgstr "沒有包含 ASN" #: documents/models.py:441 msgid "title or content contains" -msgstr "" +msgstr "標題或內容包含" #: documents/models.py:442 msgid "fulltext query" -msgstr "" +msgstr "全文搜索" #: documents/models.py:443 msgid "more like this" -msgstr "" +msgstr "其他類似內容" #: documents/models.py:444 msgid "has tags in" -msgstr "" +msgstr "含有這個標籤" #: documents/models.py:445 msgid "ASN greater than" -msgstr "" +msgstr "ASN 大於" #: documents/models.py:446 msgid "ASN less than" -msgstr "" +msgstr "ASN 小於" #: documents/models.py:447 msgid "storage path is" -msgstr "" +msgstr "儲存位址為" #: documents/models.py:448 msgid "has correspondent in" -msgstr "" +msgstr "包含聯繫者" #: documents/models.py:449 msgid "does not have correspondent in" -msgstr "" +msgstr "沒有包含聯繫者" #: documents/models.py:450 msgid "has document type in" -msgstr "" +msgstr "文件類型包含" #: documents/models.py:451 msgid "does not have document type in" -msgstr "" +msgstr "沒有包含的文件類型" #: documents/models.py:452 msgid "has storage path in" -msgstr "" +msgstr "儲存位址包含" #: documents/models.py:453 msgid "does not have storage path in" -msgstr "" +msgstr "沒有包含的儲存位址" #: documents/models.py:454 msgid "owner is" -msgstr "" +msgstr "擁有者為" #: documents/models.py:455 msgid "has owner in" -msgstr "" +msgstr "擁有者包含" #: documents/models.py:456 msgid "does not have owner" -msgstr "" +msgstr "沒有包含的擁有者" #: documents/models.py:457 msgid "does not have owner in" -msgstr "" +msgstr "沒有包含的擁有者" #: documents/models.py:467 msgid "rule type" -msgstr "" +msgstr "規則類型" #: documents/models.py:469 msgid "value" -msgstr "" +msgstr "數值" #: documents/models.py:472 msgid "filter rule" -msgstr "" +msgstr "過濾規則" #: documents/models.py:473 msgid "filter rules" -msgstr "" +msgstr "過濾規則" #: documents/models.py:584 msgid "Task ID" -msgstr "" +msgstr "任務 ID" #: documents/models.py:585 msgid "Celery ID for the Task that was run" -msgstr "" +msgstr "已執行任務的 Celery ID" #: documents/models.py:590 msgid "Acknowledged" -msgstr "" +msgstr "已確認" #: documents/models.py:591 msgid "If the task is acknowledged via the frontend or API" -msgstr "" +msgstr "如果任務已由前端 / API 確認" #: documents/models.py:597 msgid "Task Filename" -msgstr "" +msgstr "任務檔案名稱" #: documents/models.py:598 msgid "Name of the file which the Task was run for" -msgstr "" +msgstr "執行任務的目標檔案名稱" #: documents/models.py:604 msgid "Task Name" -msgstr "" +msgstr "任務名稱" #: documents/models.py:605 msgid "Name of the Task which was run" @@ -473,7 +473,7 @@ msgstr "" #: documents/models.py:612 msgid "Task State" -msgstr "" +msgstr "任務狀態" #: documents/models.py:613 msgid "Current state of the task being run" @@ -657,7 +657,7 @@ msgstr "" #: documents/models.py:967 paperless_mail/models.py:238 msgid "assign this correspondent" -msgstr "" +msgstr "指派這個聯繫者" #: documents/models.py:975 msgid "assign this storage path" @@ -1128,7 +1128,7 @@ msgstr "" #: paperless_mail/models.py:88 msgid "Do not assign a correspondent" -msgstr "" +msgstr "不要指派聯繫者" #: paperless_mail/models.py:89 msgid "Use mail address" @@ -1140,7 +1140,7 @@ msgstr "" #: paperless_mail/models.py:91 msgid "Use correspondent selected below" -msgstr "" +msgstr "使用以下已選擇的聯繫者" #: paperless_mail/models.py:101 msgid "account" @@ -1220,7 +1220,7 @@ msgstr "" #: paperless_mail/models.py:228 msgid "assign correspondent from" -msgstr "" +msgstr "指派聯繫者從" #: paperless_mail/models.py:242 msgid "Assign the rule owner to documents"