mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-19 06:18:15 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d98be3b47 | ||
|
|
6322a0bad3 | ||
|
|
70eab32503 | ||
|
|
4f91865826 | ||
|
|
a5c3e74b18 | ||
|
|
c06827acdc |
No files matched your search
@@ -76,12 +76,11 @@ jobs:
|
||||
return;
|
||||
}
|
||||
|
||||
// Reaching here with the honeypot means AI usage was not disclosed in the form, so that
|
||||
// message can name the cause.
|
||||
// A missing template label only tells us the issue did not come from the form, which
|
||||
// is reason enough to close it, but not proof of how it was written. It takes priority:
|
||||
// a hand-built body can carry the honeypot *and* a disclosure, and the form message is
|
||||
// the only one that is accurate for both. Reaching the honeypot message means the issue
|
||||
// came from the form without disclosing AI usage, so that message can name the cause.
|
||||
const body = honeypot && !bypassedTemplate
|
||||
// is reason enough to close it, but not proof of how it was written.
|
||||
const body = honeypot
|
||||
? 'This issue was automatically closed because it contains a marker that is only visible to ' +
|
||||
'automated tools, which indicates it was generated by an AI agent, but the "AI usage" field does not ' +
|
||||
'disclose this.\n\n' +
|
||||
|
||||
@@ -38,7 +38,6 @@ src/documents/bulk_edit.py:0: error: Incompatible types in assignment (expressio
|
||||
src/documents/bulk_edit.py:0: error: Invalid index type "str" for "dict[FieldDataType, str]"; expected type "FieldDataType" [index]
|
||||
src/documents/bulk_edit.py:0: error: List comprehension has incompatible type List[tuple[int, Any]]; expected List[int] [misc]
|
||||
src/documents/bulk_edit.py:0: error: List comprehension has incompatible type List[tuple[int, None]]; expected List[int] [misc]
|
||||
src/documents/bulk_edit.py:0: error: Missing named argument "p" for "remove" of "PageList" [call-arg]
|
||||
src/documents/bulk_edit.py:0: error: Missing type arguments for generic type "dict" [type-arg]
|
||||
src/documents/bulk_edit.py:0: error: Missing type arguments for generic type "dict" [type-arg]
|
||||
src/documents/bulk_edit.py:0: error: Need type annotation for "to_create" (hint: "to_create: list[<type>] = ...") [var-annotated]
|
||||
|
||||
@@ -91,13 +91,6 @@
|
||||
"concise_description": "Argument `list[int]` is not assignable to parameter `args` with type `tuple[Any, ...] | None` in function `celery.app.task.Task.apply_async`",
|
||||
"severity": "error"
|
||||
},
|
||||
{
|
||||
"column": 33,
|
||||
"path": "src/documents/bulk_edit.py",
|
||||
"name": "missing-argument",
|
||||
"concise_description": "Missing argument `p` in function `pikepdf._core.PageList.remove`",
|
||||
"severity": "error"
|
||||
},
|
||||
{
|
||||
"column": 25,
|
||||
"path": "src/documents/caching.py",
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ dependencies = [
|
||||
"drf-spectacular~=0.30",
|
||||
"drf-spectacular-sidecar>=2026.7.1,<2026.10",
|
||||
"drf-writable-nested~=0.7.1",
|
||||
"filelock>=3.32,<4.1",
|
||||
"filelock~=3.32.0",
|
||||
"flower>=2.0.1,<2.3",
|
||||
"gotenberg-client[httpx]~=1.0",
|
||||
"httpx-oauth~=0.17",
|
||||
|
||||
@@ -15,8 +15,6 @@ module.exports = {
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
useESM: true,
|
||||
// Disable the builder's deprecated override; tsconfig.spec.json enables isolation.
|
||||
isolatedModules: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+115
-126
@@ -259,15 +259,15 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">65</context>
|
||||
<context context-type="linenumber">63</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
<context context-type="linenumber">66</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
<context context-type="linenumber">69</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
|
||||
@@ -1824,7 +1824,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
@@ -2069,7 +2069,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">58</context>
|
||||
<context context-type="linenumber">54</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html</context>
|
||||
@@ -2115,7 +2115,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">224</context>
|
||||
<context context-type="linenumber">219</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="131016739441837046" datatype="html">
|
||||
@@ -2126,7 +2126,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">236</context>
|
||||
<context context-type="linenumber">231</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3643866539136239218" datatype="html">
|
||||
@@ -2162,40 +2162,22 @@
|
||||
<context context-type="linenumber">107</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4963055357149711042" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{tasks.length}}"/> of <x id="INTERPOLATION_1" equiv-text="{{count}}"/> tasks</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">126,127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2946624699882754313" datatype="html">
|
||||
<source>Show all</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">128</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">14,17</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3387705505469073896" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =1 {1 task} other {<x id="INTERPOLATION"/> tasks}}</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">131,132</context>
|
||||
<context context-type="linenumber">122</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8953033926734869941" datatype="html">
|
||||
<source>Name</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">153,154</context>
|
||||
<context context-type="linenumber">144,145</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">53</context>
|
||||
<context context-type="linenumber">49</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
@@ -2302,7 +2284,7 @@
|
||||
<source>Created</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">154,155</context>
|
||||
<context context-type="linenumber">145,146</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||
@@ -2337,21 +2319,21 @@
|
||||
<source>Results</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">156,157</context>
|
||||
<context context-type="linenumber">147,148</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="314315645942131479" datatype="html">
|
||||
<source>Info</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">158,159</context>
|
||||
<context context-type="linenumber">149,150</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3193976279273491157" datatype="html">
|
||||
<source>Actions</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">159,160</context>
|
||||
<context context-type="linenumber">150,151</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
@@ -2470,29 +2452,29 @@
|
||||
<source>click for full output</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">209</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8946500279334661567" datatype="html">
|
||||
<source>View task details</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">215</context>
|
||||
<context context-type="linenumber">207</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1536087519743707362" datatype="html">
|
||||
<source>Dismiss</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">222</context>
|
||||
<context context-type="linenumber">214</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">326</context>
|
||||
<context context-type="linenumber">321</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">360</context>
|
||||
<context context-type="linenumber">355</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
@@ -2507,28 +2489,28 @@
|
||||
<source>Open Document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">227</context>
|
||||
<context context-type="linenumber">219</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5404759957685833020" datatype="html">
|
||||
<source>Result message</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">240,241</context>
|
||||
<context context-type="linenumber">232,233</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6621329748219109148" datatype="html">
|
||||
<source>Duplicate</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">247,248</context>
|
||||
<context context-type="linenumber">239,240</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7593555694782789615" datatype="html">
|
||||
<source>Open</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">255</context>
|
||||
<context context-type="linenumber">247</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
|
||||
@@ -2559,77 +2541,77 @@
|
||||
<source>Input data</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">265,266</context>
|
||||
<context context-type="linenumber">257,258</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1585185618099050920" datatype="html">
|
||||
<source>Result data</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">271,272</context>
|
||||
<context context-type="linenumber">263,264</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7976920528153858271" datatype="html">
|
||||
<source>No tasks match the current filters.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">293,295</context>
|
||||
<context context-type="linenumber">285,287</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2525230676386818985" datatype="html">
|
||||
<source>Result</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">54</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2952127331314675199" datatype="html">
|
||||
<source>Needs attention</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">59</context>
|
||||
<context context-type="linenumber">55</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3307214558906712688" datatype="html">
|
||||
<source>In progress</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">60</context>
|
||||
<context context-type="linenumber">56</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1371178633976505412" datatype="html">
|
||||
<source>Recently completed</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">61</context>
|
||||
<context context-type="linenumber">57</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2936452645673957909" datatype="html">
|
||||
<source>Consume File</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">70</context>
|
||||
<context context-type="linenumber">66</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5622386857946132209" datatype="html">
|
||||
<source>Train Classifier</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
<context context-type="linenumber">70</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="776309955507105337" datatype="html">
|
||||
<source>Sanity Check</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">78</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7502272564743467653" datatype="html">
|
||||
<source>Mail Fetch</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
<context context-type="linenumber">76</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||
@@ -2640,63 +2622,63 @@
|
||||
<source>LLM Index</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">81</context>
|
||||
<context context-type="linenumber">77</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6402092370576716734" datatype="html">
|
||||
<source>Empty Trash</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4173754487295857311" datatype="html">
|
||||
<source>Check Workflows</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2065831805515896240" datatype="html">
|
||||
<source>Bulk Update</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5062348912423871240" datatype="html">
|
||||
<source>Reprocess Document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">96</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6903696804811148799" datatype="html">
|
||||
<source>Build Share Link</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">100</context>
|
||||
<context context-type="linenumber">96</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6581973658756561124" datatype="html">
|
||||
<source>Bulk Delete</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">104</context>
|
||||
<context context-type="linenumber">100</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5672013191898828327" datatype="html">
|
||||
<source>Apply AI Suggestions</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">108</context>
|
||||
<context context-type="linenumber">104</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9172233176401579786" datatype="html">
|
||||
<source>Scheduled</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">118</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||
@@ -2707,7 +2689,7 @@
|
||||
<source>Web UI</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">120</context>
|
||||
<context context-type="linenumber">116</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||
@@ -2718,7 +2700,7 @@
|
||||
<source>API Upload</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">123</context>
|
||||
<context context-type="linenumber">119</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||
@@ -2729,106 +2711,106 @@
|
||||
<source>Folder Consume</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
<context context-type="linenumber">123</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1456901843819961615" datatype="html">
|
||||
<source>Email Consume</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">131</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="29832309535656200" datatype="html">
|
||||
<source>System</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">133</context>
|
||||
<context context-type="linenumber">129</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2233560223291461480" datatype="html">
|
||||
<source>Manual</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">134</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5404910960991552159" datatype="html">
|
||||
<source>Dismiss selected</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">248</context>
|
||||
<context context-type="linenumber">243</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9169677036332103838" datatype="html">
|
||||
<source>Dismiss visible</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">249</context>
|
||||
<context context-type="linenumber">244</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3169751690815214293" datatype="html">
|
||||
<source>Confirm Dismiss</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">323</context>
|
||||
<context context-type="linenumber">318</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5029621907742319073" datatype="html">
|
||||
<source>Dismiss <x id="PH" equiv-text="tasks.size"/> tasks?</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">324</context>
|
||||
<context context-type="linenumber">319</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3597309129998924778" datatype="html">
|
||||
<source>Error dismissing tasks</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">335</context>
|
||||
<context context-type="linenumber">330</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">369</context>
|
||||
<context context-type="linenumber">364</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2132179171926568807" datatype="html">
|
||||
<source>Error dismissing task</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">347</context>
|
||||
<context context-type="linenumber">342</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1323591410517879795" datatype="html">
|
||||
<source>Confirm Dismiss All</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">357</context>
|
||||
<context context-type="linenumber">352</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4157200209636243740" datatype="html">
|
||||
<source>Dismiss all <x id="PH" equiv-text="this.totalTasks()"/> tasks?</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">358</context>
|
||||
<context context-type="linenumber">353</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8149502458056418229" datatype="html">
|
||||
<source>Success. New document id <x id="PH" equiv-text="documentId"/> created</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">417</context>
|
||||
<context context-type="linenumber">412</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8760066891202884337" datatype="html">
|
||||
<source>Duplicate of document #<x id="PH" equiv-text="duplicateOf"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">427</context>
|
||||
<context context-type="linenumber">422</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">461</context>
|
||||
<context context-type="linenumber">456</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3418677553313974490" datatype="html">
|
||||
@@ -3095,11 +3077,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">146</context>
|
||||
<context context-type="linenumber">145</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">199</context>
|
||||
<context context-type="linenumber">198</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/document-attributes/custom-fields/custom-fields.component.ts</context>
|
||||
@@ -3374,39 +3356,39 @@
|
||||
<source>Saved user "<x id="PH" equiv-text="newUser.username"/>".</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
<context context-type="linenumber">125</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3471101514724661554" datatype="html">
|
||||
<source>Error saving user.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5565868288871970148" datatype="html">
|
||||
<source>Confirm delete user account</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">144</context>
|
||||
<context context-type="linenumber">143</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8133663925694885325" datatype="html">
|
||||
<source>This operation will permanently delete this user account.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">145</context>
|
||||
<context context-type="linenumber">144</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1181910457994920507" datatype="html">
|
||||
<source>Proceed</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">148</context>
|
||||
<context context-type="linenumber">147</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
<context context-type="linenumber">200</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
@@ -3457,56 +3439,56 @@
|
||||
<source>Deleted user "<x id="PH" equiv-text="user.username"/>"</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">154</context>
|
||||
<context context-type="linenumber">153</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="286457042048584728" datatype="html">
|
||||
<source>Error deleting user "<x id="PH" equiv-text="user.username"/>".</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">161</context>
|
||||
<context context-type="linenumber">160</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5766640174051730159" datatype="html">
|
||||
<source>Saved group "<x id="PH" equiv-text="newGroup.name"/>".</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">181</context>
|
||||
<context context-type="linenumber">180</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8382042988405122578" datatype="html">
|
||||
<source>Error saving group.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">189</context>
|
||||
<context context-type="linenumber">188</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6538873300613683004" datatype="html">
|
||||
<source>Confirm delete user group</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">197</context>
|
||||
<context context-type="linenumber">196</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7710984639498518244" datatype="html">
|
||||
<source>This operation will permanently delete this user group.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">198</context>
|
||||
<context context-type="linenumber">197</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3756187211130340490" datatype="html">
|
||||
<source>Deleted group "<x id="PH" equiv-text="group.name"/>"</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">207</context>
|
||||
<context context-type="linenumber">206</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1697803415975901060" datatype="html">
|
||||
<source>Error deleting group "<x id="PH" equiv-text="group.name"/>".</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">214</context>
|
||||
<context context-type="linenumber">213</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7931334600001636863" datatype="html">
|
||||
@@ -3599,7 +3581,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">46</context>
|
||||
<context context-type="linenumber">47</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
@@ -3784,7 +3766,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">125</context>
|
||||
<context context-type="linenumber">123</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||
@@ -3997,7 +3979,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
<context context-type="linenumber">103</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
@@ -7417,64 +7399,64 @@
|
||||
<source>Error saving profile</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">222</context>
|
||||
<context context-type="linenumber">221</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="154249228726292516" datatype="html">
|
||||
<source>Error generating auth token</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">240</context>
|
||||
<context context-type="linenumber">239</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4153637646944982460" datatype="html">
|
||||
<source>Error disconnecting social account</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">267</context>
|
||||
<context context-type="linenumber">266</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5939111172212776886" datatype="html">
|
||||
<source>Error fetching TOTP settings</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">286</context>
|
||||
<context context-type="linenumber">285</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1030314492414713260" datatype="html">
|
||||
<source>TOTP activated successfully</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">310</context>
|
||||
<context context-type="linenumber">309</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3755006064892435830" datatype="html">
|
||||
<source>Error activating TOTP</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">312</context>
|
||||
<context context-type="linenumber">311</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">318</context>
|
||||
<context context-type="linenumber">317</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5919827473541889422" datatype="html">
|
||||
<source>TOTP deactivated successfully</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">334</context>
|
||||
<context context-type="linenumber">333</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6214722303383624015" datatype="html">
|
||||
<source>Error deactivating TOTP</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">336</context>
|
||||
<context context-type="linenumber">335</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">341</context>
|
||||
<context context-type="linenumber">340</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4452415362871534100" datatype="html">
|
||||
@@ -7749,21 +7731,21 @@
|
||||
<source>Show suggestions</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">23,24</context>
|
||||
<context context-type="linenumber">24,25</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3834115140127576673" datatype="html">
|
||||
<source>No novel suggestions</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">30,31</context>
|
||||
<context context-type="linenumber">31,32</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4369111787961525769" datatype="html">
|
||||
<source>Document Types</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">40</context>
|
||||
<context context-type="linenumber">41</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
|
||||
@@ -7774,42 +7756,42 @@
|
||||
<source>{VAR_PLURAL, plural, =1 {1 suggestion available below} other {<x id="INTERPOLATION"/> suggestions available below}}</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">53</context>
|
||||
<context context-type="linenumber">54</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4169875112244042673" datatype="html">
|
||||
<source>Suggestion options</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">63,64</context>
|
||||
<context context-type="linenumber">65,66</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3145824396228856507" datatype="html">
|
||||
<source>Not using default</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">67,68</context>
|
||||
<context context-type="linenumber">69,70</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8102623853570619759" datatype="html">
|
||||
<source>Suggest using:</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">72,73</context>
|
||||
<context context-type="linenumber">74,75</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5799391967036896978" datatype="html">
|
||||
<source>ML</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">75</context>
|
||||
<context context-type="linenumber">77</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3484984238852181071" datatype="html">
|
||||
<source>AI</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
<context context-type="linenumber">79</context>
|
||||
<context context-type="linenumber">81</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9180110319941008393" datatype="html">
|
||||
@@ -8105,6 +8087,13 @@
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2946624699882754313" datatype="html">
|
||||
<source>Show all</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">14,17</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1559883523769732271" datatype="html">
|
||||
<source>Error while loading documents</source>
|
||||
<context-group purpose="location">
|
||||
@@ -8120,7 +8109,7 @@
|
||||
<source>Filter by correspondent</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">75</context>
|
||||
<context context-type="linenumber">73</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
@@ -8135,7 +8124,7 @@
|
||||
<source>Filter by document type</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
<context context-type="linenumber">83</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
@@ -8150,7 +8139,7 @@
|
||||
<source>Filter by storage path</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">90</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
@@ -8165,14 +8154,14 @@
|
||||
<source>Filter by owner</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">95</context>
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3542042671420335679" datatype="html">
|
||||
<source>No</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
<context context-type="linenumber">103</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
@@ -8183,7 +8172,7 @@
|
||||
<source>No documents</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
|
||||
<context context-type="linenumber">189,192</context>
|
||||
<context context-type="linenumber">187,190</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1069523139277190436" datatype="html">
|
||||
@@ -8511,7 +8500,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/main.ts</context>
|
||||
<context context-type="linenumber">475</context>
|
||||
<context context-type="linenumber">473</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5028777105388019087" datatype="html">
|
||||
@@ -13230,14 +13219,14 @@
|
||||
<source>Prev</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/main.ts</context>
|
||||
<context context-type="linenumber">474</context>
|
||||
<context context-type="linenumber">472</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1241348629231510663" datatype="html">
|
||||
<source>End</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/main.ts</context>
|
||||
<context context-type="linenumber">476</context>
|
||||
<context context-type="linenumber">474</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
</body>
|
||||
|
||||
@@ -102,34 +102,25 @@
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (selectedSection !== TaskSection.All && totalTasks() > pageSize) {
|
||||
<ngb-pagination
|
||||
[pageSize]="pageSize"
|
||||
[collectionSize]="totalTasks()"
|
||||
[page]="page"
|
||||
[maxSize]="5"
|
||||
[rotate]="true"
|
||||
size="sm"
|
||||
aria-label="Tasks pagination"
|
||||
(pageChange)="setPage($event)">
|
||||
</ngb-pagination>
|
||||
}
|
||||
<ngb-pagination
|
||||
[pageSize]="pageSize"
|
||||
[collectionSize]="totalTasks()"
|
||||
[page]="page"
|
||||
[maxSize]="5"
|
||||
[rotate]="true"
|
||||
size="sm"
|
||||
aria-label="Tasks pagination"
|
||||
(pageChange)="setPage($event)">
|
||||
</ngb-pagination>
|
||||
</div>
|
||||
|
||||
<ng-template let-tasks="tasks" let-section="section" #tasksTemplate>
|
||||
<div class="section-header mb-2">
|
||||
<h5 class="mb-0">{{ sectionLabel(section) }}</h5>
|
||||
@let count = sectionCount(section);
|
||||
@let hasMore = selectedSection === TaskSection.All && count > tasks.length;
|
||||
<div class="small text-muted">
|
||||
@if (hasMore) {
|
||||
<ng-container i18n>{{tasks.length}} of {{count}} tasks</ng-container>
|
||||
<button class="btn btn-link show-all p-0 ms-2 align-baseline" type="button" (click)="setSection(section)">
|
||||
<ng-container i18n>Show all</ng-container><i-bs class="ms-1" width="1em" height="1em" name="arrow-right"></i-bs>
|
||||
</button>
|
||||
} @else {
|
||||
<ng-container i18n>{count, plural, =1 {1 task} other {{{count}} tasks}}</ng-container>
|
||||
}
|
||||
<div class="section-header d-flex align-items-center justify-content-between mb-2">
|
||||
<div>
|
||||
<h5 class="mb-0">{{ sectionLabel(section) }}</h5>
|
||||
<div class="small text-muted">
|
||||
<ng-container i18n>{tasks.length, plural, =1 {1 task} other {{{tasks.length}} tasks}}</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -178,9 +169,10 @@
|
||||
<div>{{ taskDisplayName(task) }}</div>
|
||||
<div class="small text-muted">
|
||||
@if (taskShowsSeparateTypeLabel(task)) {
|
||||
<span class="me-2">{{ task.task_type_display }}</span>
|
||||
<span>{{ task.task_type_display }}</span>
|
||||
<span class="mx-1">•</span>
|
||||
}
|
||||
<span class="text-nowrap"><i-bs class="me-1" width="1em" height="1em" name="box-arrow-in-right"></i-bs>{{ task.trigger_source_display }}</span>
|
||||
<span>{{ task.trigger_source_display }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="d-none d-lg-table-cell created-col">{{ task.date_created | customDate:'short' }}</td>
|
||||
|
||||
@@ -72,7 +72,3 @@ pre {
|
||||
tbody tr:nth-last-child(2) td {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.section-header .show-all {
|
||||
font-size: inherit;
|
||||
}
|
||||
@@ -223,6 +223,16 @@ describe('TasksComponent', () => {
|
||||
)
|
||||
.flush(paginatedTasks)
|
||||
|
||||
httpTestingController
|
||||
.expectOne(
|
||||
(req) =>
|
||||
req.url === `${environment.apiBaseUrl}tasks/` &&
|
||||
req.params.get('acknowledged') === 'false' &&
|
||||
req.params.get('page_size') === '25' &&
|
||||
req.params.get('page') === '1'
|
||||
)
|
||||
.flush(paginatedTasks)
|
||||
|
||||
httpTestingController
|
||||
.expectOne(
|
||||
(req) =>
|
||||
@@ -231,30 +241,8 @@ describe('TasksComponent', () => {
|
||||
!req.params.has('status')
|
||||
)
|
||||
.flush(sectionCountResponse)
|
||||
|
||||
flushTaskLists(10)
|
||||
})
|
||||
|
||||
// answers task list requests with the fixture tasks matching their status filter
|
||||
function flushTaskLists(pageSize: number, count?: number) {
|
||||
httpTestingController
|
||||
.match(
|
||||
(req) =>
|
||||
req.url === `${environment.apiBaseUrl}tasks/` &&
|
||||
req.params.get('page_size') === `${pageSize}`
|
||||
)
|
||||
.forEach((req) => {
|
||||
const statuses = req.request.params.getAll('status')
|
||||
const results = tasks.filter((task) => statuses.includes(task.status))
|
||||
req.flush({ count: count ?? results.length, results })
|
||||
})
|
||||
}
|
||||
|
||||
function selectSection(section: TaskSection, count: number) {
|
||||
component.setSection(section)
|
||||
flushTaskLists(25, count)
|
||||
}
|
||||
|
||||
it('should display task sections with counts', () => {
|
||||
expect(component.selectedSection).toBe(TaskSection.All)
|
||||
expect(component.selectedTaskType).toBeNull()
|
||||
@@ -350,7 +338,6 @@ describe('TasksComponent', () => {
|
||||
})
|
||||
|
||||
it('should render pagination controls next to the task filter', () => {
|
||||
selectSection(TaskSection.Completed, 30)
|
||||
fixture.detectChanges()
|
||||
|
||||
const controls = fixture.debugElement.query(By.css('.task-controls'))
|
||||
@@ -361,51 +348,6 @@ describe('TasksComponent', () => {
|
||||
expect(pagination).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should only paginate a section with more than one page of tasks', () => {
|
||||
const pagination = () =>
|
||||
fixture.debugElement.query(By.css('ngb-pagination'))
|
||||
|
||||
fixture.detectChanges()
|
||||
expect(pagination()).toBeNull()
|
||||
|
||||
selectSection(TaskSection.Completed, 2)
|
||||
fixture.detectChanges()
|
||||
expect(pagination()).toBeNull()
|
||||
})
|
||||
|
||||
it('should load the all tasks overview one section at a time', () => {
|
||||
expect(component.tasksForSection(TaskSection.NeedsAttention)).toHaveLength(
|
||||
2
|
||||
)
|
||||
expect(component.tasksForSection(TaskSection.InProgress)).toHaveLength(3)
|
||||
expect(component.tasksForSection(TaskSection.Completed)).toHaveLength(2)
|
||||
expect(component.totalTasks()).toBe(7)
|
||||
})
|
||||
|
||||
it('should show section totals and link truncated sections to their full list', () => {
|
||||
component.sectionCounts.update((counts) => ({
|
||||
...counts,
|
||||
[TaskSection.Completed]: 27,
|
||||
}))
|
||||
fixture.detectChanges()
|
||||
|
||||
const header = (label: string) =>
|
||||
fixture.debugElement
|
||||
.queryAll(By.css('.section-header'))
|
||||
.find((h) => h.nativeElement.textContent.includes(label))
|
||||
|
||||
expect(header('Needs attention').nativeElement.textContent).toContain(
|
||||
'2 tasks'
|
||||
)
|
||||
expect(header('Needs attention').query(By.css('button'))).toBeNull()
|
||||
|
||||
expect(header('Recently completed').nativeElement.textContent).toContain(
|
||||
'2 of 27 tasks'
|
||||
)
|
||||
header('Recently completed').query(By.css('button')).nativeElement.click()
|
||||
expect(component.selectedSection).toBe(TaskSection.Completed)
|
||||
})
|
||||
|
||||
it('should apply the selected section to the server-side task query', () => {
|
||||
component.setSection(TaskSection.NeedsAttention)
|
||||
|
||||
@@ -426,56 +368,55 @@ describe('TasksComponent', () => {
|
||||
it('should apply task type and trigger source filters to the server-side task query', () => {
|
||||
component.setTaskType(PaperlessTaskType.SanityCheck)
|
||||
|
||||
expect(
|
||||
httpTestingController.match(
|
||||
httpTestingController
|
||||
.expectOne(
|
||||
(request) =>
|
||||
request.url === `${environment.apiBaseUrl}tasks/` &&
|
||||
request.params.get('page_size') === '10' &&
|
||||
request.params.get('page_size') === '25' &&
|
||||
request.params.get('task_type') === PaperlessTaskType.SanityCheck
|
||||
)
|
||||
).toHaveLength(3)
|
||||
.flush({ count: 1, results: [tasks[6]] })
|
||||
|
||||
component.setTriggerSource(PaperlessTaskTriggerSource.System)
|
||||
|
||||
expect(
|
||||
httpTestingController.match(
|
||||
httpTestingController
|
||||
.expectOne(
|
||||
(request) =>
|
||||
request.url === `${environment.apiBaseUrl}tasks/` &&
|
||||
request.params.get('page_size') === '10' &&
|
||||
request.params.get('page_size') === '25' &&
|
||||
request.params.get('task_type') === PaperlessTaskType.SanityCheck &&
|
||||
request.params.get('trigger_source') ===
|
||||
PaperlessTaskTriggerSource.System
|
||||
)
|
||||
).toHaveLength(3)
|
||||
.flush({ count: 1, results: [tasks[6]] })
|
||||
})
|
||||
|
||||
it('should apply text filters to the server-side task query', () => {
|
||||
component.filterText = 'invoice'
|
||||
jest.advanceTimersByTime(150)
|
||||
|
||||
expect(
|
||||
httpTestingController.match(
|
||||
httpTestingController
|
||||
.expectOne(
|
||||
(request) =>
|
||||
request.url === `${environment.apiBaseUrl}tasks/` &&
|
||||
request.params.get('page_size') === '10' &&
|
||||
request.params.get('page_size') === '25' &&
|
||||
request.params.get('name') === 'invoice'
|
||||
)
|
||||
).toHaveLength(3)
|
||||
.flush({ count: 1, results: [tasks[0]] })
|
||||
|
||||
component.setFilterTarget(TaskFilterTargetID.Result)
|
||||
|
||||
expect(
|
||||
httpTestingController.match(
|
||||
httpTestingController
|
||||
.expectOne(
|
||||
(request) =>
|
||||
request.url === `${environment.apiBaseUrl}tasks/` &&
|
||||
request.params.get('page_size') === '10' &&
|
||||
request.params.get('page_size') === '25' &&
|
||||
request.params.get('result') === 'invoice'
|
||||
)
|
||||
).toHaveLength(3)
|
||||
.flush({ count: 0, results: [] })
|
||||
})
|
||||
|
||||
it('should load a different task page when pagination changes', () => {
|
||||
selectSection(TaskSection.Completed, 30)
|
||||
component.setPage(2)
|
||||
|
||||
const pageTwoTasks = {
|
||||
@@ -499,7 +440,6 @@ describe('TasksComponent', () => {
|
||||
})
|
||||
|
||||
it('should not replace section counts with current-page counts', () => {
|
||||
selectSection(TaskSection.Completed, 30)
|
||||
component.setPage(2)
|
||||
|
||||
httpTestingController
|
||||
@@ -515,21 +455,11 @@ describe('TasksComponent', () => {
|
||||
results: [tasks[0]],
|
||||
})
|
||||
|
||||
expect(component.sectionCount(TaskSection.All)).toBe(7)
|
||||
expect(component.sectionCount(TaskSection.NeedsAttention)).toBe(2)
|
||||
expect(component.sectionCount(TaskSection.InProgress)).toBe(3)
|
||||
expect(component.sectionCount(TaskSection.Completed)).toBe(2)
|
||||
})
|
||||
|
||||
it('should stop loading when the overview fails to load', () => {
|
||||
component.setSection(TaskSection.All)
|
||||
httpTestingController
|
||||
.match((req) => req.params.get('page_size') === '10')[0]
|
||||
.flush('', { status: 500, statusText: 'Error' })
|
||||
|
||||
expect(component.loading()).toBe(false)
|
||||
})
|
||||
|
||||
it('should expose stable task type options and disable empty ones', () => {
|
||||
expect(component.taskTypeOptions.map((option) => option.value)).toContain(
|
||||
PaperlessTaskType.TrainClassifier
|
||||
|
||||
@@ -15,9 +15,6 @@ import {
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
first,
|
||||
forkJoin,
|
||||
map,
|
||||
Observable,
|
||||
Subject,
|
||||
takeUntil,
|
||||
timer,
|
||||
@@ -28,7 +25,6 @@ import {
|
||||
PaperlessTaskTriggerSource,
|
||||
PaperlessTaskType,
|
||||
} from 'src/app/data/paperless-task'
|
||||
import { Results } from 'src/app/data/results'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { TasksService } from 'src/app/services/tasks.service'
|
||||
@@ -173,7 +169,6 @@ export class TasksComponent
|
||||
public expandedTask: number
|
||||
public autoRefreshEnabled: boolean = true
|
||||
public readonly pageSize = 25
|
||||
public readonly overviewSize = 10
|
||||
public page: number = 1
|
||||
readonly totalTasks = signal(0)
|
||||
readonly sectionCounts = signal<Record<TaskSection, number>>({
|
||||
@@ -489,6 +484,13 @@ export class TasksComponent
|
||||
return this.sectionCounts()[section]
|
||||
}
|
||||
|
||||
private setSectionCount(section: TaskSection, count: number) {
|
||||
this.sectionCounts.update((counts) => ({
|
||||
...counts,
|
||||
[section]: count,
|
||||
}))
|
||||
}
|
||||
|
||||
sectionShowsResults(section: TaskSection): boolean {
|
||||
return section !== TaskSection.InProgress
|
||||
}
|
||||
@@ -735,12 +737,21 @@ export class TasksComponent
|
||||
this.reloadSectionCounts()
|
||||
|
||||
this.loading.set(true)
|
||||
this.listTasks()
|
||||
this.tasksService
|
||||
.list(
|
||||
this.page,
|
||||
this.pageSize,
|
||||
this.getParamsForSection(this.selectedSection)
|
||||
)
|
||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.pagedTasks.set(result.results)
|
||||
this.totalTasks.set(result.count)
|
||||
this.setSectionCount(TaskSection.All, result.count)
|
||||
if (this.selectedSection !== TaskSection.All) {
|
||||
this.setSectionCount(this.selectedSection, result.count)
|
||||
}
|
||||
this.loading.set(false)
|
||||
if (
|
||||
this.page > 1 &&
|
||||
@@ -756,30 +767,4 @@ export class TasksComponent
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private listTasks(): Observable<Results<PaperlessTask>> {
|
||||
if (this.selectedSection !== TaskSection.All) {
|
||||
return this.tasksService.list(
|
||||
this.page,
|
||||
this.pageSize,
|
||||
this.getParamsForSection(this.selectedSection)
|
||||
)
|
||||
}
|
||||
|
||||
// All is an overview of each section to avoid splitting sections across pages
|
||||
return forkJoin(
|
||||
this.sections.map((section) =>
|
||||
this.tasksService.list(
|
||||
1,
|
||||
this.overviewSize,
|
||||
this.getParamsForSection(section)
|
||||
)
|
||||
)
|
||||
).pipe(
|
||||
map((results) => ({
|
||||
count: results.reduce((sum, result) => sum + result.count, 0),
|
||||
results: results.flatMap((result) => result.results),
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -154,31 +154,6 @@ describe('UsersAndGroupsComponent', () => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('should logout current user under base href if password changed', () => {
|
||||
jest.useFakeTimers()
|
||||
const base = document.createElement('base')
|
||||
base.href = '/paperless/'
|
||||
document.head.appendChild(base)
|
||||
completeSetup()
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.editUser(users[0])
|
||||
const navSpy = jest
|
||||
.spyOn(navUtils, 'setLocationHref')
|
||||
.mockImplementation(() => {})
|
||||
const editDialog = modal.componentInstance as UserEditDialogComponent
|
||||
editDialog.passwordIsSet = true
|
||||
settingsService.currentUser.set(users[0]) // simulate logged in as same user
|
||||
editDialog.succeeded.emit(users[0])
|
||||
fixture.detectChanges()
|
||||
jest.advanceTimersByTime(2600)
|
||||
expect(navSpy).toHaveBeenCalledWith(
|
||||
`${window.location.origin}/paperless/accounts/logout/?next=/paperless/accounts/login/?next=/paperless/`
|
||||
)
|
||||
base.remove()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('should support edit / create group, show error if needed', () => {
|
||||
completeSetup()
|
||||
let modal: NgbModalRef
|
||||
|
||||
@@ -116,9 +116,8 @@ export class UsersAndGroupsComponent
|
||||
$localize`Password has been changed, you will be logged out momentarily.`
|
||||
)
|
||||
setTimeout(() => {
|
||||
const basePath = new URL(document.baseURI).pathname
|
||||
setLocationHref(
|
||||
`${window.location.origin}${basePath}accounts/logout/?next=${basePath}accounts/login/?next=${basePath}`
|
||||
`${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||
)
|
||||
}, 2500)
|
||||
} else {
|
||||
|
||||
-32
@@ -218,38 +218,6 @@ describe('ProfileEditDialogComponent', () => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('should logout under base href on save if password changed', () => {
|
||||
jest.useFakeTimers()
|
||||
const base = document.createElement('base')
|
||||
base.href = '/paperless/'
|
||||
document.head.appendChild(base)
|
||||
const getSpy = jest.spyOn(profileService, 'get')
|
||||
getSpy.mockReturnValue(of(profile))
|
||||
const getProvidersSpy = jest.spyOn(
|
||||
profileService,
|
||||
'getSocialAccountProviders'
|
||||
)
|
||||
getProvidersSpy.mockReturnValue(of(socialAccountProviders))
|
||||
component.ngOnInit()
|
||||
component['newPassword'] = 'new*pass'
|
||||
component.form.get('password').patchValue('new*pass')
|
||||
component.form.get('password_confirm').patchValue('new*pass')
|
||||
|
||||
const updateSpy = jest.spyOn(profileService, 'update')
|
||||
updateSpy.mockReturnValue(of(null))
|
||||
const navSpy = jest
|
||||
.spyOn(navUtils, 'setLocationHref')
|
||||
.mockImplementation(() => {})
|
||||
component.save()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
jest.advanceTimersByTime(2600)
|
||||
expect(navSpy).toHaveBeenCalledWith(
|
||||
`${window.location.origin}/paperless/accounts/logout/?next=/paperless/accounts/login/?next=/paperless/`
|
||||
)
|
||||
base.remove()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('should support auth token copy', () => {
|
||||
jest.useFakeTimers()
|
||||
const getSpy = jest.spyOn(profileService, 'get')
|
||||
|
||||
+1
-2
@@ -210,9 +210,8 @@ export class ProfileEditDialogComponent
|
||||
$localize`Password has been changed, you will be logged out momentarily.`
|
||||
)
|
||||
setTimeout(() => {
|
||||
const basePath = new URL(document.baseURI).pathname
|
||||
setLocationHref(
|
||||
`${window.location.origin}${basePath}accounts/logout/?next=${basePath}accounts/login/?next=${basePath}`
|
||||
`${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||
)
|
||||
}, 2500)
|
||||
}
|
||||
|
||||
+35
-33
@@ -1,6 +1,6 @@
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading()" [aria-label]="noSuggestions ? 'No suggestions' : 'Suggest'" i18n-aria-label>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading() || (suggestions() && !aiEnabled())" [aria-label]="noSuggestions ? 'No suggestions' : 'Suggest'" i18n-aria-label>
|
||||
@if (loading()) {
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
} @else if (noSuggestions) {
|
||||
@@ -18,44 +18,46 @@
|
||||
}
|
||||
</button>
|
||||
|
||||
<div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
|
||||
<span class="visually-hidden" i18n>Show suggestions</span>
|
||||
</button>
|
||||
@if (aiEnabled()) {
|
||||
<div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
|
||||
<span class="visually-hidden" i18n>Show suggestions</span>
|
||||
</button>
|
||||
|
||||
<div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown">
|
||||
<div class="list-group list-group-flush small pb-0">
|
||||
@if (novelSuggestions === 0 && fieldSuggestions === 0) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>No novel suggestions</small>
|
||||
</div>
|
||||
}
|
||||
@if (suggestions()?.suggested_tags?.length > 0) {
|
||||
<small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
|
||||
@for (tag of suggestions().suggested_tags; track tag) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
|
||||
<div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown">
|
||||
<div class="list-group list-group-flush small pb-0">
|
||||
@if (novelSuggestions === 0 && fieldSuggestions === 0) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>No novel suggestions</small>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (suggestions()?.suggested_document_types?.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div>
|
||||
@for (type of suggestions().suggested_document_types; track type) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button>
|
||||
@if (suggestions()?.suggested_tags?.length > 0) {
|
||||
<small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
|
||||
@for (tag of suggestions().suggested_tags; track tag) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (suggestions()?.suggested_correspondents?.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div>
|
||||
@for (correspondent of suggestions().suggested_correspondents; track correspondent) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button>
|
||||
@if (suggestions()?.suggested_document_types?.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div>
|
||||
@for (type of suggestions().suggested_document_types; track type) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (fieldSuggestions > 0) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>{fieldSuggestions, plural, =1 {1 suggestion available below} other {{{fieldSuggestions}} suggestions available below}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (suggestions()?.suggested_correspondents?.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div>
|
||||
@for (correspondent of suggestions().suggested_correspondents; track correspondent) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button>
|
||||
}
|
||||
}
|
||||
@if (fieldSuggestions > 0) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>{fieldSuggestions, plural, =1 {1 suggestion available below} other {{{fieldSuggestions}} suggestions available below}}</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (aiEnabled()) {
|
||||
|
||||
-31
@@ -251,37 +251,6 @@ describe('SuggestionsDropdownComponent', () => {
|
||||
expect(fixture.nativeElement.textContent).toContain('No novel suggestions')
|
||||
})
|
||||
|
||||
it('should open ML date suggestions with AI disabled', () => {
|
||||
const getSuggestions = jest.spyOn(component.getSuggestions, 'emit')
|
||||
fixture.componentRef.setInput('aiEnabled', false)
|
||||
fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
|
||||
fixture.componentRef.setInput('suggestions', {
|
||||
dates: ['2026-01-04', '2026-02-01'],
|
||||
})
|
||||
fixture.componentRef.setInput('appliedCreated', '2026-01-04')
|
||||
fixture.detectChanges()
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button')
|
||||
expect(button.disabled).toBeFalsy()
|
||||
expect(button.querySelector('.badge').textContent.trim()).toBe('1')
|
||||
|
||||
button.click()
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(component.dropdown.isOpen()).toBeTruthy()
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'1 suggestion available below'
|
||||
)
|
||||
expect(getSuggestions).not.toHaveBeenCalled()
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('button[title="Suggestion options"]')
|
||||
).toBeNull()
|
||||
|
||||
button.click()
|
||||
fixture.detectChanges()
|
||||
expect(component.dropdown.isOpen()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should fetch unfetched sources and show existing suggestions', () => {
|
||||
jest.spyOn(component.getSuggestions, 'emit')
|
||||
fixture.componentRef.setInput('aiEnabled', true)
|
||||
|
||||
+5
-1
@@ -53,7 +53,11 @@ export class SuggestionsDropdownComponent {
|
||||
addCorrespondent: EventEmitter<string> = new EventEmitter()
|
||||
|
||||
public clickSuggest(): void {
|
||||
if (this.disabled() || this.loading()) {
|
||||
if (
|
||||
this.disabled() ||
|
||||
this.loading() ||
|
||||
(this.suggestions() && !this.aiEnabled())
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -25,8 +25,6 @@
|
||||
@if (displayFields().includes(field)) {
|
||||
<th
|
||||
scope="col"
|
||||
class="text-truncate"
|
||||
[title]="getColumnTitle(field)"
|
||||
[ngClass]="{
|
||||
'd-none d-md-table-cell': i > 1,
|
||||
'w-25': field === DisplayField.CREATED || field === DisplayField.ADDED
|
||||
|
||||
+10
-46
@@ -484,22 +484,6 @@ describe('FilterEditorComponent', () => {
|
||||
expect(component.textFilter).toBeNull()
|
||||
})
|
||||
|
||||
it('should ingest title filter rules alongside a fulltext query with date created', () => {
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_SIMPLE_TITLE,
|
||||
value: 'foo',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: 'created:[-1 week to now]',
|
||||
},
|
||||
]
|
||||
expect(component.dateCreatedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||
expect(component.textFilter).toEqual('foo')
|
||||
expect(component.textFilterTarget).toEqual('title') // TEXT_FILTER_TARGET_TITLE
|
||||
})
|
||||
|
||||
it('should ingest text filter rules for fulltext query that include date added', () => {
|
||||
expect(component.dateAddedRelativeDate).toBeNull()
|
||||
component.filterRules = [
|
||||
@@ -1861,7 +1845,7 @@ describe('FilterEditorComponent', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should keep text filtering separate from date created relative date', () => {
|
||||
it('should carry over text filtering on date created with relative date', () => {
|
||||
component.textFilter = 'foo'
|
||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(DatesDropdownComponent)
|
||||
@@ -1871,18 +1855,14 @@ describe('FilterEditorComponent', () => {
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_SIMPLE_TEXT,
|
||||
value: 'foo',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: 'created:[-1 week to now]',
|
||||
value: 'foo,created:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should keep text filtering separate from created and added relative dates', () => {
|
||||
it('should carry over text filtering once with created and added relative dates', () => {
|
||||
component.textFilter = 'foo'
|
||||
const datesDropdown = fixture.debugElement.query(
|
||||
By.directive(DatesDropdownComponent)
|
||||
@@ -1893,18 +1873,14 @@ describe('FilterEditorComponent', () => {
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_SIMPLE_TEXT,
|
||||
value: 'foo',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: 'created:[-1 week to now],added:[-1 month to now]',
|
||||
value: 'foo,created:[-1 week to now],added:[-1 month to now]',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should keep legacy title filters as a title search when adding a created relative date', () => {
|
||||
it('should convert legacy title filters into full text query when adding a created relative date', () => {
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_TITLE,
|
||||
@@ -1919,18 +1895,14 @@ describe('FilterEditorComponent', () => {
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_SIMPLE_TITLE,
|
||||
value: 'foo',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: 'created:[-1 week to now]',
|
||||
value: 'foo,created:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should keep simple title filters as a title search when adding a created relative date', () => {
|
||||
it('should convert simple title filters into full text query when adding a created relative date', () => {
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_SIMPLE_TITLE,
|
||||
@@ -1945,13 +1917,9 @@ describe('FilterEditorComponent', () => {
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_SIMPLE_TITLE,
|
||||
value: 'foo',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: 'created:[-1 week to now]',
|
||||
value: 'foo,created:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
})
|
||||
@@ -2037,7 +2005,7 @@ describe('FilterEditorComponent', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should keep text filtering separate from date added relative date', () => {
|
||||
it('should carry over text filtering on date added with relative date', () => {
|
||||
component.textFilter = 'foo'
|
||||
const datesDropdown = fixture.debugElement.query(
|
||||
By.directive(DatesDropdownComponent)
|
||||
@@ -2047,13 +2015,9 @@ describe('FilterEditorComponent', () => {
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_SIMPLE_TEXT,
|
||||
value: 'foo',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: 'added:[-1 week to now]',
|
||||
value: 'foo,added:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
@@ -1083,6 +1083,23 @@ export class FilterEditorComponent
|
||||
(fr) => fr.rule_type == FILTER_FULLTEXT_QUERY
|
||||
)
|
||||
|
||||
// if had a title / content search and added a relative date we need to carry it over...
|
||||
if (
|
||||
!existingRule &&
|
||||
this._textFilter?.length > 0 &&
|
||||
(this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT ||
|
||||
this.textFilterTarget == TEXT_FILTER_TARGET_TITLE)
|
||||
) {
|
||||
existingRule = filterRules.find(
|
||||
(fr) =>
|
||||
fr.rule_type == FILTER_TITLE_CONTENT ||
|
||||
fr.rule_type == FILTER_SIMPLE_TEXT ||
|
||||
fr.rule_type == FILTER_TITLE ||
|
||||
fr.rule_type == FILTER_SIMPLE_TITLE
|
||||
)
|
||||
existingRule.rule_type = FILTER_FULLTEXT_QUERY
|
||||
}
|
||||
|
||||
let queryArgs = existingRule?.value.split(',') ?? []
|
||||
if (this.dateCreatedRelativeDate !== null) {
|
||||
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
|
||||
|
||||
+3
-5
@@ -11,7 +11,7 @@
|
||||
<div class="input-group input-group-sm w-auto d-none d-md-flex">
|
||||
<label class="input-group-text bg-transparent border-0" for="managementPageSize" i18n>Show:</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm w-auto">
|
||||
<div class="input-group input-group-sm w-auto me-3">
|
||||
<select id="managementPageSize" class="form-select form-select-sm small" [(ngModel)]="pageSize">
|
||||
<option [ngValue]="25">25</option>
|
||||
<option [ngValue]="50">50</option>
|
||||
@@ -19,9 +19,7 @@
|
||||
</select>
|
||||
<span class="input-group-text text-muted d-none d-md-flex" i18n>per page</span>
|
||||
</div>
|
||||
@if (collectionSize() > pageSize) {
|
||||
<ngb-pagination [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" class="ms-3" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
}
|
||||
<ngb-pagination [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +70,7 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (collectionSize() > pageSize) {
|
||||
@if (collectionSize() > 20) {
|
||||
<ngb-pagination class="ms-auto" [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
}
|
||||
</div>
|
||||
|
||||
+9
-11
@@ -93,17 +93,15 @@
|
||||
[disabled]="selectedMailIds().size === 0"
|
||||
(confirm)="deleteSelected()">
|
||||
</pngx-confirm-button>
|
||||
@if (processedMails().length > 50) {
|
||||
<div class="ms-auto">
|
||||
<ngb-pagination
|
||||
[collectionSize]="processedMails().length"
|
||||
[(page)]="page"
|
||||
[pageSize]="50"
|
||||
[maxSize]="5"
|
||||
(pageChange)="loadProcessedMails()">
|
||||
</ngb-pagination>
|
||||
</div>
|
||||
}
|
||||
<div class="ms-auto">
|
||||
<ngb-pagination
|
||||
[collectionSize]="processedMails().length"
|
||||
[(page)]="page"
|
||||
[pageSize]="50"
|
||||
[maxSize]="5"
|
||||
(pageChange)="loadProcessedMails()">
|
||||
</ngb-pagination>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -17,7 +17,7 @@
|
||||
<context context-type="linenumber">386,393</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Currently selected slide number read by screen reader</note>
|
||||
<target state="translated"> スライド <x id="INTERPOLATION" equiv-text="xt(source?: Ngb"/> / <x id="INTERPOLATION_1" equiv-text="this._cycleToSe"/> </target>
|
||||
<target state="needs-translation"> Slide <x id="INTERPOLATION" equiv-text="xt(source?: Ngb"/> of <x id="INTERPOLATION_1" equiv-text="this._cycleToSe"/> </target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ngb.carousel.previous" datatype="html">
|
||||
<source>Previous</source>
|
||||
@@ -479,7 +479,7 @@
|
||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
<target state="translated">タグ、取引先、ドキュメントの種類、保存先パス、カスタムフィールドなどの属性をここで管理できます。これらはドキュメントの編集画面から作成することもできます。</target>
|
||||
<target state="needs-translation">Attributes like tags, correspondents, document types, storage paths and custom fields can all be managed here. They can also be created from the document edit view.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7851939076947092983" datatype="html">
|
||||
<source>Manage e-mail accounts and rules for automatically importing documents.</source>
|
||||
@@ -507,7 +507,7 @@
|
||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||
<context context-type="linenumber">225</context>
|
||||
</context-group>
|
||||
<target state="translated">タスクでは、バックグラウンド処理の進行状況、対応が必要な項目、最近完了した処理を確認できます。</target>
|
||||
<target state="needs-translation">Tasks helps you track background work, what needs attention, and what recently completed.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2755033621682206902" datatype="html">
|
||||
<source>Check out the settings for various tweaks to the web app.</source>
|
||||
@@ -571,7 +571,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
</context-group>
|
||||
<target state="translated">この値は、Paperless の外で設定されている <x id="INTERPOLATION" equiv-text="{{option.config_key}}"/>を上書きします。</target>
|
||||
<target state="needs-translation">This value overrides <x id="INTERPOLATION" equiv-text="{{option.config_key}}"/>, which is set outside Paperless.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7221396516204435584" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</source>
|
||||
@@ -579,7 +579,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||
<context context-type="linenumber">37</context>
|
||||
</context-group>
|
||||
<target state="translated"><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> はPaperlessの外部で設定されています。ここに値を入力すると、その設定を上書きできます。</target>
|
||||
<target state="needs-translation"><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8318849619178340389" datatype="html">
|
||||
<source>Use the externally configured value</source>
|
||||
@@ -587,7 +587,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
</context-group>
|
||||
<target state="translated">外部で設定された値を使用</target>
|
||||
<target state="needs-translation">Use the externally configured value</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6032629623003430385" datatype="html">
|
||||
<source>Reset to external</source>
|
||||
@@ -595,7 +595,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||
<context context-type="linenumber">43</context>
|
||||
</context-group>
|
||||
<target state="translated">外部設定に戻す</target>
|
||||
<target state="needs-translation">Reset to external</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7808756054397155068" datatype="html">
|
||||
<source>Reset</source>
|
||||
@@ -875,7 +875,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">18,19</context>
|
||||
</context-group>
|
||||
<target state="translated">行</target>
|
||||
<target state="needs-translation">lines</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8838884664569764142" datatype="html">
|
||||
<source>Auto refresh</source>
|
||||
@@ -1003,7 +1003,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">63,64</context>
|
||||
</context-group>
|
||||
<target state="translated">一番下へ移動</target>
|
||||
<target state="needs-translation">Jump to bottom</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1255048712725285892" datatype="html">
|
||||
<source>Options to customize appearance, notifications and more. Settings apply to the <strong>current user only</strong>.</source>
|
||||
@@ -1135,7 +1135,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">115,116</context>
|
||||
</context-group>
|
||||
<target state="translated">サイドバーに表示するアイテム:</target>
|
||||
<target state="needs-translation">Sidebar items to show:</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1356890996281769972" datatype="html">
|
||||
<source>Dark mode</source>
|
||||
@@ -1323,7 +1323,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
<target state="translated">サイドバーの保存済みビューにドキュメント数を表示</target>
|
||||
<target state="needs-translation">Show document counts in sidebar saved views</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8939587804990976924" datatype="html">
|
||||
<source>Items per page</source>
|
||||
@@ -1403,7 +1403,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">259,260</context>
|
||||
</context-group>
|
||||
<target state="translated">提案機能のデフォルト設定</target>
|
||||
<target state="needs-translation">Suggestions default to</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6251586038459969294" datatype="html">
|
||||
<source>Both</source>
|
||||
@@ -1411,7 +1411,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">264</context>
|
||||
</context-group>
|
||||
<target state="translated">両方</target>
|
||||
<target state="needs-translation">Both</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8967224283122923634" datatype="html">
|
||||
<source>ML only</source>
|
||||
@@ -1427,7 +1427,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">268</context>
|
||||
</context-group>
|
||||
<target state="translated">AIのみ</target>
|
||||
<target state="needs-translation">AI only</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1043449647727456069" datatype="html">
|
||||
<source>Automatically request suggestions for inbox documents</source>
|
||||
@@ -1435,7 +1435,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">276</context>
|
||||
</context-group>
|
||||
<target state="translated">受信トレイ内のドキュメントに対して自動的に提案をリクエストする</target>
|
||||
<target state="needs-translation">Automatically request suggestions for inbox documents</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="460700039378563125" datatype="html">
|
||||
<source>If un-checked, suggestions must be requested via the Suggest button.</source>
|
||||
@@ -1443,7 +1443,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">276</context>
|
||||
</context-group>
|
||||
<target state="translated">チェックを外した場合、提案を取得するには「提案」ボタンを押す必要があります。</target>
|
||||
<target state="needs-translation">If un-checked, suggestions must be requested via the Suggest button.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8793267604636304297" datatype="html">
|
||||
<source>Show document thumbnail during loading</source>
|
||||
@@ -1467,7 +1467,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">300,301</context>
|
||||
</context-group>
|
||||
<target state="translated">ドキュメントの詳細ページで非表示にするフィールドは、チェックを外してください。</target>
|
||||
<target state="needs-translation">Uncheck fields to hide them on the document details page.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8508424367627989968" datatype="html">
|
||||
<source>Bulk editing</source>
|
||||
@@ -1507,7 +1507,7 @@
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">1907</context>
|
||||
</context-group>
|
||||
<target state="translated">PDF エディター</target>
|
||||
<target state="needs-translation">PDF Editor</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1577733187050997705" datatype="html">
|
||||
<source>Default editing mode</source>
|
||||
@@ -1515,7 +1515,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">317,318</context>
|
||||
</context-group>
|
||||
<target state="translated">デフォルトの編集モード</target>
|
||||
<target state="needs-translation">Default editing mode</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7273640930165035289" datatype="html">
|
||||
<source>Create new document(s)</source>
|
||||
@@ -1527,7 +1527,7 @@
|
||||
<context context-type="sourcefile">src/app/components/common/pdf-editor/pdf-editor.component.html</context>
|
||||
<context context-type="linenumber">82,83</context>
|
||||
</context-group>
|
||||
<target state="translated">新しいドキュメントを作成</target>
|
||||
<target state="needs-translation">Create new document(s)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7860582931776068318" datatype="html">
|
||||
<source>Add document version</source>
|
||||
@@ -1539,7 +1539,7 @@
|
||||
<context context-type="sourcefile">src/app/components/common/pdf-editor/pdf-editor.component.html</context>
|
||||
<context context-type="linenumber">87,88</context>
|
||||
</context-group>
|
||||
<target state="translated">ドキュメントのバージョンを追加</target>
|
||||
<target state="needs-translation">Add document version</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8104421162933956065" datatype="html">
|
||||
<source>Notes</source>
|
||||
@@ -1639,7 +1639,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">347</context>
|
||||
</context-group>
|
||||
<target state="translated">これらの設定は、Web UIから作成されたオブジェクト(タグ、メールルールなど)に対して、このユーザーアカウントに適用されます。ドキュメントには適用されません。</target>
|
||||
<target state="needs-translation"> Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI. These settings do not apply to documents. </target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4292903881380648974" datatype="html">
|
||||
<source>Default Owner</source>
|
||||
@@ -2203,7 +2203,7 @@
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">331</context>
|
||||
</context-group>
|
||||
<target state="translated">タスク</target>
|
||||
<target state="needs-translation">Tasks</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8492095365580052820" datatype="html">
|
||||
<source>Tasks shows detailed information about document consumption and system tasks.</source>
|
||||
@@ -2211,7 +2211,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">4,5</context>
|
||||
</context-group>
|
||||
<target state="translated">タスクでは、ドキュメントの取り込み処理やシステムタスクに関する詳細情報を確認できます。</target>
|
||||
<target state="needs-translation">Tasks shows detailed information about document consumption and system tasks.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="103921551219467537" datatype="html">
|
||||
<source>Clear selection</source>
|
||||
@@ -2235,7 +2235,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">16</context>
|
||||
</context-group>
|
||||
<target state="translated">すべて閉じる</target>
|
||||
<target state="needs-translation">Dismiss all</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1616102757855967475" datatype="html">
|
||||
<source>All</source>
|
||||
@@ -2295,7 +2295,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">219</context>
|
||||
</context-group>
|
||||
<target state="translated">すべての種類</target>
|
||||
<target state="needs-translation">All types</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="131016739441837046" datatype="html">
|
||||
<source>All sources</source>
|
||||
@@ -2307,7 +2307,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">231</context>
|
||||
</context-group>
|
||||
<target state="translated">すべてのソース</target>
|
||||
<target state="needs-translation">All sources</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3643866539136239218" datatype="html">
|
||||
<source>Clear search</source>
|
||||
@@ -2327,7 +2327,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">92,93</context>
|
||||
</context-group>
|
||||
<target state="translated">タスクを検索</target>
|
||||
<target state="needs-translation">Search tasks</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6849725902312323996" datatype="html">
|
||||
<source>Reset filters</source>
|
||||
@@ -2351,7 +2351,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">122</context>
|
||||
</context-group>
|
||||
<target state="translated">{VAR_PLURAL, plural, =1 {1件のタスク} other {<x id="INTERPOLATION"/>件のタスク}}</target>
|
||||
<target state="needs-translation">{VAR_PLURAL, plural, =1 {1 task} other {<x id="INTERPOLATION"/> tasks}}</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8953033926734869941" datatype="html">
|
||||
<source>Name</source>
|
||||
@@ -2651,7 +2651,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">207</context>
|
||||
</context-group>
|
||||
<target state="translated">タスクの詳細を表示</target>
|
||||
<target state="needs-translation">View task details</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1536087519743707362" datatype="html">
|
||||
<source>Dismiss</source>
|
||||
@@ -2691,7 +2691,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">232,233</context>
|
||||
</context-group>
|
||||
<target state="translated">結果メッセージ</target>
|
||||
<target state="needs-translation">Result message</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6621329748219109148" datatype="html">
|
||||
<source>Duplicate</source>
|
||||
@@ -2699,7 +2699,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">239,240</context>
|
||||
</context-group>
|
||||
<target state="translated">重複</target>
|
||||
<target state="needs-translation">Duplicate</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7593555694782789615" datatype="html">
|
||||
<source>Open</source>
|
||||
@@ -2739,7 +2739,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">257,258</context>
|
||||
</context-group>
|
||||
<target state="translated">入力データ</target>
|
||||
<target state="needs-translation">Input data</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1585185618099050920" datatype="html">
|
||||
<source>Result data</source>
|
||||
@@ -2747,7 +2747,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">263,264</context>
|
||||
</context-group>
|
||||
<target state="translated">結果データ</target>
|
||||
<target state="needs-translation">Result data</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7976920528153858271" datatype="html">
|
||||
<source>No tasks match the current filters.</source>
|
||||
@@ -2755,7 +2755,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">285,287</context>
|
||||
</context-group>
|
||||
<target state="translated">現在のフィルター条件に一致するタスクはありません。</target>
|
||||
<target state="needs-translation">No tasks match the current filters.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2525230676386818985" datatype="html">
|
||||
<source>Result</source>
|
||||
@@ -2771,7 +2771,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">55</context>
|
||||
</context-group>
|
||||
<target state="translated">注意が必要</target>
|
||||
<target state="needs-translation">Needs attention</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3307214558906712688" datatype="html">
|
||||
<source>In progress</source>
|
||||
@@ -2779,7 +2779,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">56</context>
|
||||
</context-group>
|
||||
<target state="translated">進行中</target>
|
||||
<target state="needs-translation">In progress</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1371178633976505412" datatype="html">
|
||||
<source>Recently completed</source>
|
||||
@@ -2787,7 +2787,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">57</context>
|
||||
</context-group>
|
||||
<target state="translated">最近完了したタスク</target>
|
||||
<target state="needs-translation">Recently completed</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2936452645673957909" datatype="html">
|
||||
<source>Consume File</source>
|
||||
@@ -2795,7 +2795,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">66</context>
|
||||
</context-group>
|
||||
<target state="translated">ファイルの取り込み</target>
|
||||
<target state="needs-translation">Consume File</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5622386857946132209" datatype="html">
|
||||
<source>Train Classifier</source>
|
||||
@@ -2811,7 +2811,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
<target state="translated">整合性チェック</target>
|
||||
<target state="needs-translation">Sanity Check</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7502272564743467653" datatype="html">
|
||||
<source>Mail Fetch</source>
|
||||
@@ -2839,7 +2839,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
<target state="translated">ゴミ箱を空にする</target>
|
||||
<target state="needs-translation">Empty Trash</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4173754487295857311" datatype="html">
|
||||
<source>Check Workflows</source>
|
||||
@@ -2847,7 +2847,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
</context-group>
|
||||
<target state="translated">ワークフローの確認</target>
|
||||
<target state="needs-translation">Check Workflows</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2065831805515896240" datatype="html">
|
||||
<source>Bulk Update</source>
|
||||
@@ -2855,7 +2855,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
</context-group>
|
||||
<target state="translated">一括更新</target>
|
||||
<target state="needs-translation">Bulk Update</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5062348912423871240" datatype="html">
|
||||
<source>Reprocess Document</source>
|
||||
@@ -2863,7 +2863,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
<target state="translated">ドキュメントの再処理</target>
|
||||
<target state="needs-translation">Reprocess Document</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6903696804811148799" datatype="html">
|
||||
<source>Build Share Link</source>
|
||||
@@ -2871,7 +2871,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">96</context>
|
||||
</context-group>
|
||||
<target state="translated">共有リンクの生成</target>
|
||||
<target state="needs-translation">Build Share Link</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6581973658756561124" datatype="html">
|
||||
<source>Bulk Delete</source>
|
||||
@@ -2879,7 +2879,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">100</context>
|
||||
</context-group>
|
||||
<target state="translated">一括削除</target>
|
||||
<target state="needs-translation">Bulk Delete</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5672013191898828327" datatype="html">
|
||||
<source>Apply AI Suggestions</source>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<context context-type="linenumber">386,393</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Currently selected slide number read by screen reader</note>
|
||||
<target state="translated"> Shlid <x id="INTERPOLATION" equiv-text="xt(source?: Ngb"/> i <x id="INTERPOLATION_1" equiv-text="this._cycleToSe"/></target>
|
||||
<target state="needs-translation"> Slide <x id="INTERPOLATION" equiv-text="xt(source?: Ngb"/> of <x id="INTERPOLATION_1" equiv-text="this._cycleToSe"/> </target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ngb.carousel.previous" datatype="html">
|
||||
<source>Previous</source>
|
||||
@@ -571,7 +571,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
</context-group>
|
||||
<target state="translated">Ky vlerë mbizotëron <x id="INTERPOLATION" equiv-text="{{option.config_key}}"/>, i cili është vendosur jashtë Paperless.</target>
|
||||
<target state="needs-translation">This value overrides <x id="INTERPOLATION" equiv-text="{{option.config_key}}"/>, which is set outside Paperless.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7221396516204435584" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</source>
|
||||
@@ -579,7 +579,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||
<context context-type="linenumber">37</context>
|
||||
</context-group>
|
||||
<target state="translated"><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> është vendosur jashtë Paperless. Shkruani një vlerë këtu për ta mbingarkuar atë.</target>
|
||||
<target state="needs-translation"><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8318849619178340389" datatype="html">
|
||||
<source>Use the externally configured value</source>
|
||||
@@ -587,7 +587,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
</context-group>
|
||||
<target state="translated">Përdorni vlerën e konfiguruar nga jashtë</target>
|
||||
<target state="needs-translation">Use the externally configured value</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6032629623003430385" datatype="html">
|
||||
<source>Reset to external</source>
|
||||
@@ -595,7 +595,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||
<context context-type="linenumber">43</context>
|
||||
</context-group>
|
||||
<target state="translated">Rinisni në të jashtëm</target>
|
||||
<target state="needs-translation">Reset to external</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7808756054397155068" datatype="html">
|
||||
<source>Reset</source>
|
||||
@@ -1135,7 +1135,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">115,116</context>
|
||||
</context-group>
|
||||
<target state="translated">Elementët e shiritit anësor për t'u shfaqur:</target>
|
||||
<target state="needs-translation">Sidebar items to show:</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1356890996281769972" datatype="html">
|
||||
<source>Dark mode</source>
|
||||
@@ -1403,7 +1403,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">259,260</context>
|
||||
</context-group>
|
||||
<target state="translated">Sugjerimet parazgjedhore janë</target>
|
||||
<target state="needs-translation">Suggestions default to</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6251586038459969294" datatype="html">
|
||||
<source>Both</source>
|
||||
@@ -1411,7 +1411,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">264</context>
|
||||
</context-group>
|
||||
<target state="translated">Të dyja</target>
|
||||
<target state="needs-translation">Both</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8967224283122923634" datatype="html">
|
||||
<source>ML only</source>
|
||||
@@ -1419,7 +1419,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">266</context>
|
||||
</context-group>
|
||||
<target state="translated">Vetëm ML</target>
|
||||
<target state="needs-translation">ML only</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6418969597665857901" datatype="html">
|
||||
<source>AI only</source>
|
||||
@@ -1427,7 +1427,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">268</context>
|
||||
</context-group>
|
||||
<target state="translated">Vetëm AI</target>
|
||||
<target state="needs-translation">AI only</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1043449647727456069" datatype="html">
|
||||
<source>Automatically request suggestions for inbox documents</source>
|
||||
@@ -1435,7 +1435,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">276</context>
|
||||
</context-group>
|
||||
<target state="translated">Kërkoni automatikisht sugjerime për dokumentet në kutinë e postës elektronike</target>
|
||||
<target state="needs-translation">Automatically request suggestions for inbox documents</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="460700039378563125" datatype="html">
|
||||
<source>If un-checked, suggestions must be requested via the Suggest button.</source>
|
||||
@@ -1443,7 +1443,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">276</context>
|
||||
</context-group>
|
||||
<target state="translated">Nëse nuk është shënuar, sugjerimet duhet të kërkohen përmes butonit Sugjero.</target>
|
||||
<target state="needs-translation">If un-checked, suggestions must be requested via the Suggest button.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8793267604636304297" datatype="html">
|
||||
<source>Show document thumbnail during loading</source>
|
||||
@@ -2319,7 +2319,7 @@
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
<target state="translated">Pastro kërkimin</target>
|
||||
<target state="needs-translation">Clear search</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3570013039254960445" datatype="html">
|
||||
<source>Search tasks</source>
|
||||
@@ -2651,7 +2651,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">207</context>
|
||||
</context-group>
|
||||
<target state="translated">Shiko detajet e detyrës</target>
|
||||
<target state="needs-translation">View task details</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1536087519743707362" datatype="html">
|
||||
<source>Dismiss</source>
|
||||
@@ -2887,7 +2887,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">104</context>
|
||||
</context-group>
|
||||
<target state="translated">Apliko sugjerimet e AI</target>
|
||||
<target state="needs-translation">Apply AI Suggestions</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="9172233176401579786" datatype="html">
|
||||
<source>Scheduled</source>
|
||||
@@ -3783,7 +3783,7 @@
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
</context-group>
|
||||
<target state="translated">Menuja e përdoruesit</target>
|
||||
<target state="needs-translation">User menu</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2448391510242468907" datatype="html">
|
||||
<source>Logged in as <x id="INTERPOLATION" equiv-text="{{this.settingsService.displayName}}"/></source>
|
||||
@@ -3939,7 +3939,7 @@
|
||||
<context context-type="sourcefile">src/app/components/manage/share-links/share-links.component.html</context>
|
||||
<context context-type="linenumber">2,3</context>
|
||||
</context-group>
|
||||
<target state="translated">Ndani lidhjet</target>
|
||||
<target state="needs-translation">Share links</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7844706011418789951" datatype="html">
|
||||
<source>Administration</source>
|
||||
@@ -4015,7 +4015,7 @@
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">403</context>
|
||||
</context-group>
|
||||
<target state="translated">Konfiguro kontrollin e përditësimeve</target>
|
||||
<target state="needs-translation">Configure update checking</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1542489069631984294" datatype="html">
|
||||
<source>Sidebar views updated</source>
|
||||
@@ -4259,7 +4259,7 @@
|
||||
<context context-type="sourcefile">src/app/data/saved-view-icons.ts</context>
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
<target state="translated">Bisedë</target>
|
||||
<target state="needs-translation">Chat</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8066608938393600549" datatype="html">
|
||||
<source>Message</source>
|
||||
@@ -4379,7 +4379,7 @@
|
||||
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-as-versions-confirm-dialog/merge-as-versions-confirm-dialog.component.html</context>
|
||||
<context context-type="linenumber">8,9</context>
|
||||
</context-group>
|
||||
<target state="translated">Dokumenti rrënjësor:</target>
|
||||
<target state="needs-translation">Root document:</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2121026240713807342" datatype="html">
|
||||
<source>Versions (oldest first):</source>
|
||||
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
bell,
|
||||
bodyText,
|
||||
bookmark,
|
||||
boxArrowInRight,
|
||||
boxArrowUp,
|
||||
boxArrowUpRight,
|
||||
boxes,
|
||||
@@ -294,7 +293,6 @@ const icons = {
|
||||
braces,
|
||||
bodyText,
|
||||
bookmark,
|
||||
boxArrowInRight,
|
||||
boxArrowUp,
|
||||
boxArrowUpRight,
|
||||
boxes,
|
||||
|
||||
+184
-219
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import tempfile
|
||||
import uuid
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
@@ -23,6 +24,7 @@ from django.db.models import When
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils import timezone
|
||||
|
||||
from documents import pdf_ops
|
||||
from documents.data_models import ConsumableDocument
|
||||
from documents.data_models import DocumentMetadataOverrides
|
||||
from documents.data_models import DocumentSource
|
||||
@@ -123,6 +125,11 @@ def _resolve_root_and_source_doc(
|
||||
)
|
||||
|
||||
|
||||
def _scratch_path(name: str) -> Path:
|
||||
"""A path inside a fresh directory under SCRATCH_DIR."""
|
||||
return Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR)) / name
|
||||
|
||||
|
||||
def set_correspondent(
|
||||
doc_ids: list[int],
|
||||
correspondent: Correspondent,
|
||||
@@ -499,8 +506,6 @@ def rotate(
|
||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||
docs_by_root_id.setdefault(pair.root_doc.id, pair)
|
||||
|
||||
import pikepdf
|
||||
|
||||
for pair in docs_by_root_id.values():
|
||||
if pair.source_doc.mime_type != "application/pdf":
|
||||
logger.warning(
|
||||
@@ -513,11 +518,7 @@ def rotate(
|
||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
||||
/ f"{pair.root_doc.id}_rotated.pdf"
|
||||
)
|
||||
with pikepdf.open(pair.source_doc.source_path) as pdf:
|
||||
for page in pdf.pages:
|
||||
page.rotate(degrees, relative=True)
|
||||
pdf.remove_unreferenced_resources()
|
||||
pdf.save(filepath)
|
||||
pdf_ops.rotate_pdf(pair.source_doc.source_path, filepath, degrees)
|
||||
|
||||
# Preserve metadata/permissions via overrides; mark as new version
|
||||
overrides = DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||
@@ -560,48 +561,45 @@ def merge(
|
||||
qs = Document.objects.select_related("root_document").filter(id__in=doc_ids)
|
||||
docs_by_id = {doc.id: doc for doc in qs}
|
||||
affected_docs: list[int] = []
|
||||
import pikepdf
|
||||
|
||||
merged_pdf = pikepdf.new()
|
||||
version: str = merged_pdf.pdf_version
|
||||
handoff_asn: int | None = None
|
||||
# use doc_ids to preserve order
|
||||
for doc_id in doc_ids:
|
||||
doc = docs_by_id.get(doc_id)
|
||||
if doc is None:
|
||||
continue
|
||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||
try:
|
||||
doc_path = (
|
||||
pair.source_doc.archive_path
|
||||
if archive_fallback
|
||||
and pair.source_doc.mime_type != "application/pdf"
|
||||
and pair.source_doc.has_archive_version
|
||||
else pair.source_doc.source_path
|
||||
)
|
||||
with pikepdf.open(str(doc_path)) as pdf:
|
||||
version = max(version, pdf.pdf_version)
|
||||
merged_pdf.pages.extend(pdf.pages)
|
||||
affected_docs.append(doc.id)
|
||||
if handoff_asn is None and doc.archive_serial_number is not None:
|
||||
handoff_asn = doc.archive_serial_number
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"Error merging document {doc.id}, it will not be included in the merge: {e}",
|
||||
)
|
||||
if len(affected_docs) == 0:
|
||||
logger.warning("No documents were merged")
|
||||
return "OK"
|
||||
with pdf_ops.PdfMerger() as merger:
|
||||
# use doc_ids to preserve order
|
||||
for doc_id in doc_ids:
|
||||
doc = docs_by_id.get(doc_id)
|
||||
if doc is None:
|
||||
continue
|
||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||
try:
|
||||
# archive_path is None when there is no archive version
|
||||
archive_path = (
|
||||
pair.source_doc.archive_path
|
||||
if archive_fallback
|
||||
and pair.source_doc.mime_type != "application/pdf"
|
||||
else None
|
||||
)
|
||||
merger.add(
|
||||
archive_path
|
||||
if archive_path is not None
|
||||
else pair.source_doc.source_path,
|
||||
)
|
||||
affected_docs.append(doc.id)
|
||||
if handoff_asn is None and doc.archive_serial_number is not None:
|
||||
handoff_asn = doc.archive_serial_number
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"Error merging document {doc.id}, it will not be included in the merge: {e}",
|
||||
)
|
||||
if len(affected_docs) == 0:
|
||||
logger.warning("No documents were merged")
|
||||
return "OK"
|
||||
|
||||
filepath = (
|
||||
Path(
|
||||
tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
|
||||
filepath = (
|
||||
Path(
|
||||
tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
|
||||
)
|
||||
/ f"{'_'.join([str(doc_id) for doc_id in affected_docs])[:100]}_merged.pdf"
|
||||
)
|
||||
/ f"{'_'.join([str(doc_id) for doc_id in affected_docs])[:100]}_merged.pdf"
|
||||
)
|
||||
merged_pdf.remove_unreferenced_resources()
|
||||
merged_pdf.save(filepath, min_version=version)
|
||||
merged_pdf.close()
|
||||
merger.save(filepath)
|
||||
|
||||
if metadata_document_id:
|
||||
metadata_document = qs.get(id=metadata_document_id)
|
||||
@@ -777,64 +775,60 @@ def split(
|
||||
)
|
||||
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||
import pikepdf
|
||||
|
||||
consume_tasks = []
|
||||
|
||||
try:
|
||||
with pikepdf.open(pair.source_doc.source_path) as pdf:
|
||||
for idx, split_doc in enumerate(pages):
|
||||
dst: pikepdf.Pdf = pikepdf.new()
|
||||
for page in split_doc:
|
||||
dst.pages.append(pdf.pages[page - 1])
|
||||
filepath: Path = (
|
||||
Path(
|
||||
tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
|
||||
)
|
||||
/ f"{doc.id}_{split_doc[0]}-{split_doc[-1]}.pdf"
|
||||
)
|
||||
dst.remove_unreferenced_resources()
|
||||
dst.save(filepath)
|
||||
dst.close()
|
||||
outputs = [
|
||||
(
|
||||
[pdf_ops.PageSpec(page) for page in split_doc],
|
||||
partial(_scratch_path, f"{doc.id}_{split_doc[0]}-{split_doc[-1]}.pdf"),
|
||||
)
|
||||
for split_doc in pages
|
||||
]
|
||||
filepaths = pdf_ops.build_pdfs(pair.source_doc.source_path, outputs)
|
||||
|
||||
overrides: DocumentMetadataOverrides = (
|
||||
DocumentMetadataOverrides().from_document(doc)
|
||||
)
|
||||
overrides.title = f"{doc.title} (split {idx + 1})"
|
||||
if user is not None:
|
||||
overrides.owner_id = user.id
|
||||
if not delete_originals:
|
||||
overrides.skip_asn_if_exists = True
|
||||
logger.info(
|
||||
f"Adding split document with pages {split_doc} to the task queue.",
|
||||
)
|
||||
consume_tasks.append(
|
||||
consume_file.s(
|
||||
input_doc=ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=filepath,
|
||||
),
|
||||
overrides=overrides,
|
||||
).set(headers={"trigger_source": trigger_source}),
|
||||
)
|
||||
for idx, (split_doc, filepath) in enumerate(
|
||||
zip(pages, filepaths, strict=True),
|
||||
):
|
||||
overrides: DocumentMetadataOverrides = (
|
||||
DocumentMetadataOverrides().from_document(doc)
|
||||
)
|
||||
overrides.title = f"{doc.title} (split {idx + 1})"
|
||||
if user is not None:
|
||||
overrides.owner_id = user.id
|
||||
if not delete_originals:
|
||||
overrides.skip_asn_if_exists = True
|
||||
logger.info(
|
||||
f"Adding split document with pages {split_doc} to the task queue.",
|
||||
)
|
||||
consume_tasks.append(
|
||||
consume_file.s(
|
||||
input_doc=ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=filepath,
|
||||
),
|
||||
overrides=overrides,
|
||||
).set(headers={"trigger_source": trigger_source}),
|
||||
)
|
||||
|
||||
if delete_originals:
|
||||
backup = release_archive_serial_numbers([doc.id])
|
||||
logger.info(
|
||||
"Queueing removal of original document after consumption of the split documents",
|
||||
)
|
||||
try:
|
||||
chord(
|
||||
header=consume_tasks,
|
||||
body=delete.si([doc.id]),
|
||||
).on_error(
|
||||
restore_archive_serial_numbers_task.s(backup),
|
||||
).apply_async()
|
||||
except Exception:
|
||||
restore_archive_serial_numbers(backup)
|
||||
raise
|
||||
else:
|
||||
group(consume_tasks).delay()
|
||||
if delete_originals:
|
||||
backup = release_archive_serial_numbers([doc.id])
|
||||
logger.info(
|
||||
"Queueing removal of original document after consumption of the split documents",
|
||||
)
|
||||
try:
|
||||
chord(
|
||||
header=consume_tasks,
|
||||
body=delete.si([doc.id]),
|
||||
).on_error(
|
||||
restore_archive_serial_numbers_task.s(backup),
|
||||
).apply_async()
|
||||
except Exception:
|
||||
restore_archive_serial_numbers(backup)
|
||||
raise
|
||||
else:
|
||||
group(consume_tasks).delay()
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error splitting document {doc.id}: {e}")
|
||||
@@ -855,8 +849,6 @@ def delete_pages(
|
||||
)
|
||||
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||
pages = sorted(pages) # sort pages to avoid index issues
|
||||
import pikepdf
|
||||
|
||||
try:
|
||||
# Produce edited PDF to a temp file and create a new version
|
||||
@@ -864,13 +856,7 @@ def delete_pages(
|
||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
||||
/ f"{pair.root_doc.id}_pages_deleted.pdf"
|
||||
)
|
||||
with pikepdf.open(pair.source_doc.source_path) as pdf:
|
||||
offset = 1 # pages are 1-indexed
|
||||
for page_num in pages:
|
||||
pdf.pages.remove(pdf.pages[page_num - offset])
|
||||
offset += 1 # remove() changes the index of the pages
|
||||
pdf.remove_unreferenced_resources()
|
||||
pdf.save(filepath)
|
||||
pdf_ops.remove_pages(pair.source_doc.source_path, filepath, pages)
|
||||
|
||||
overrides = DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||
if user is not None:
|
||||
@@ -919,47 +905,28 @@ def edit_pdf(
|
||||
)
|
||||
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||
import pikepdf
|
||||
|
||||
pdf_docs: list[pikepdf.Pdf] = []
|
||||
|
||||
try:
|
||||
if not operations:
|
||||
raise ValueError("Output document index is out of bounds")
|
||||
|
||||
max_idx = max(op.get("doc", 0) for op in operations)
|
||||
if update_document and max_idx > 0:
|
||||
logger.error(
|
||||
"Update requested but multiple output documents specified",
|
||||
output_count = pdf_ops.validate_page_operations(
|
||||
operations,
|
||||
single_output=update_document,
|
||||
)
|
||||
page_specs: list[list[pdf_ops.PageSpec]] = [[] for _ in range(output_count)]
|
||||
for op in operations:
|
||||
page_specs[op.get("doc", 0)].append(
|
||||
pdf_ops.PageSpec(op["page"], op.get("rotate", 0)),
|
||||
)
|
||||
raise ValueError("Multiple output documents specified")
|
||||
|
||||
if any(
|
||||
op.get("doc", 0) < 0 or op.get("doc", 0) >= len(operations)
|
||||
for op in operations
|
||||
):
|
||||
raise ValueError("Output document index is out of bounds")
|
||||
|
||||
with pikepdf.open(pair.source_doc.source_path) as src:
|
||||
# prepare output documents
|
||||
pdf_docs = [pikepdf.new() for _ in range(max_idx + 1)]
|
||||
|
||||
for op in operations:
|
||||
dst = pdf_docs[op.get("doc", 0)]
|
||||
page = src.pages[op["page"] - 1]
|
||||
dst.pages.append(page)
|
||||
if op.get("rotate"):
|
||||
dst.pages[-1].rotate(op["rotate"], relative=True)
|
||||
|
||||
if update_document:
|
||||
# Create a new version from the edited PDF rather than replacing in-place
|
||||
pdf = pdf_docs[0]
|
||||
pdf.remove_unreferenced_resources()
|
||||
filepath: Path = (
|
||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
||||
/ f"{pair.root_doc.id}_edited.pdf"
|
||||
(filepath,) = pdf_ops.build_pdfs(
|
||||
pair.source_doc.source_path,
|
||||
[
|
||||
(
|
||||
page_specs[0],
|
||||
partial(_scratch_path, f"{pair.root_doc.id}_edited.pdf"),
|
||||
),
|
||||
],
|
||||
)
|
||||
pdf.save(filepath)
|
||||
overrides = (
|
||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||
if include_metadata
|
||||
@@ -980,6 +947,19 @@ def edit_pdf(
|
||||
headers={"trigger_source": trigger_source},
|
||||
)
|
||||
else:
|
||||
version_filepaths = pdf_ops.build_pdfs(
|
||||
pair.source_doc.source_path,
|
||||
[
|
||||
(
|
||||
specs,
|
||||
partial(
|
||||
_scratch_path,
|
||||
f"{pair.root_doc.id}_edit_{idx}.pdf",
|
||||
),
|
||||
)
|
||||
for idx, specs in enumerate(page_specs, start=1)
|
||||
],
|
||||
)
|
||||
consume_tasks = []
|
||||
overrides = (
|
||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||
@@ -991,15 +971,9 @@ def edit_pdf(
|
||||
overrides.actor_id = user.id
|
||||
if not delete_original:
|
||||
overrides.skip_asn_if_exists = True
|
||||
if delete_original and len(pdf_docs) == 1:
|
||||
if delete_original and output_count == 1:
|
||||
overrides.asn = pair.root_doc.archive_serial_number
|
||||
for idx, pdf in enumerate(pdf_docs, start=1):
|
||||
version_filepath: Path = (
|
||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
||||
/ f"{pair.root_doc.id}_edit_{idx}.pdf"
|
||||
)
|
||||
pdf.remove_unreferenced_resources()
|
||||
pdf.save(version_filepath)
|
||||
for version_filepath in version_filepaths:
|
||||
consume_tasks.append(
|
||||
consume_file.s(
|
||||
input_doc=ConsumableDocument(
|
||||
@@ -1049,8 +1023,6 @@ def remove_password(
|
||||
"""
|
||||
Remove password protection from PDF documents.
|
||||
"""
|
||||
import pikepdf
|
||||
|
||||
for doc_id in doc_ids:
|
||||
doc = Document.objects.select_related("root_document").get(id=doc_id)
|
||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||
@@ -1064,76 +1036,69 @@ def remove_password(
|
||||
doc.id,
|
||||
pair.source_doc.source_path,
|
||||
)
|
||||
try:
|
||||
with pikepdf.open(source_path) as pdf:
|
||||
if not pdf.is_encrypted:
|
||||
logger.info(
|
||||
"Skipping password removal for document %s because the "
|
||||
"source PDF is not encrypted",
|
||||
pair.root_doc.id,
|
||||
)
|
||||
continue
|
||||
except pikepdf.PasswordError:
|
||||
# Password-protected PDFs need the supplied password below.
|
||||
pass
|
||||
|
||||
with pikepdf.open(source_path, password=password) as pdf:
|
||||
filepath: Path = (
|
||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
||||
/ f"{pair.root_doc.id}_unprotected.pdf"
|
||||
if not pdf_ops.needs_decrypt(source_path):
|
||||
logger.info(
|
||||
"Skipping password removal for document %s because the "
|
||||
"source PDF is not encrypted",
|
||||
pair.root_doc.id,
|
||||
)
|
||||
pdf.remove_unreferenced_resources()
|
||||
pdf.save(filepath)
|
||||
continue
|
||||
|
||||
if update_document:
|
||||
# Create a new version rather than modifying the root/original in place.
|
||||
overrides = (
|
||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||
if include_metadata
|
||||
else DocumentMetadataOverrides()
|
||||
)
|
||||
if user is not None:
|
||||
overrides.owner_id = user.id
|
||||
overrides.actor_id = user.id
|
||||
consume_file.apply_async(
|
||||
kwargs={
|
||||
"input_doc": ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=filepath,
|
||||
root_document_id=pair.root_doc.id,
|
||||
),
|
||||
"overrides": overrides,
|
||||
},
|
||||
headers={"trigger_source": trigger_source},
|
||||
)
|
||||
filepath = pdf_ops.decrypt_pdf(
|
||||
source_path,
|
||||
partial(_scratch_path, f"{pair.root_doc.id}_unprotected.pdf"),
|
||||
password,
|
||||
)
|
||||
|
||||
if update_document:
|
||||
# Create a new version rather than modifying the root/original in place.
|
||||
overrides = (
|
||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||
if include_metadata
|
||||
else DocumentMetadataOverrides()
|
||||
)
|
||||
if user is not None:
|
||||
overrides.owner_id = user.id
|
||||
overrides.actor_id = user.id
|
||||
consume_file.apply_async(
|
||||
kwargs={
|
||||
"input_doc": ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=filepath,
|
||||
root_document_id=pair.root_doc.id,
|
||||
),
|
||||
"overrides": overrides,
|
||||
},
|
||||
headers={"trigger_source": trigger_source},
|
||||
)
|
||||
else:
|
||||
consume_tasks = []
|
||||
overrides = (
|
||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||
if include_metadata
|
||||
else DocumentMetadataOverrides()
|
||||
)
|
||||
if user is not None:
|
||||
overrides.owner_id = user.id
|
||||
overrides.actor_id = user.id
|
||||
|
||||
consume_tasks.append(
|
||||
consume_file.s(
|
||||
input_doc=ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=filepath,
|
||||
),
|
||||
overrides=overrides,
|
||||
).set(headers={"trigger_source": trigger_source}),
|
||||
)
|
||||
|
||||
if delete_original:
|
||||
chord(
|
||||
header=consume_tasks,
|
||||
body=delete.si([doc.id]),
|
||||
).delay()
|
||||
else:
|
||||
consume_tasks = []
|
||||
overrides = (
|
||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||
if include_metadata
|
||||
else DocumentMetadataOverrides()
|
||||
)
|
||||
if user is not None:
|
||||
overrides.owner_id = user.id
|
||||
overrides.actor_id = user.id
|
||||
|
||||
consume_tasks.append(
|
||||
consume_file.s(
|
||||
input_doc=ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=filepath,
|
||||
),
|
||||
overrides=overrides,
|
||||
).set(headers={"trigger_source": trigger_source}),
|
||||
)
|
||||
|
||||
if delete_original:
|
||||
chord(
|
||||
header=consume_tasks,
|
||||
body=delete.si([doc.id]),
|
||||
).delay()
|
||||
else:
|
||||
group(consume_tasks).delay()
|
||||
group(consume_tasks).delay()
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
|
||||
@@ -54,10 +54,6 @@ def _predict_with_threshold(classifier, X, threshold: float) -> int | None:
|
||||
cutoff so that uncertain predictions are discarded rather than assigned.
|
||||
"""
|
||||
probas = classifier.predict_proba(X)[0]
|
||||
if len(classifier.classes_) == 1:
|
||||
# scikit-learn treats a single-class fit as binary, so predict_proba
|
||||
# returns [1 - p, p] for one class. Column 0 is the real class.
|
||||
probas = probas[:1]
|
||||
best_idx = int(probas.argmax())
|
||||
best_class = int(classifier.classes_[best_idx])
|
||||
|
||||
|
||||
@@ -130,14 +130,12 @@ class DocumentMetadataOverrides:
|
||||
get_users_with_perms(
|
||||
doc,
|
||||
only_with_perms_in=["view_document"],
|
||||
with_group_users=False,
|
||||
).values_list("id", flat=True),
|
||||
)
|
||||
overrides.change_users = list(
|
||||
get_users_with_perms(
|
||||
doc,
|
||||
only_with_perms_in=["change_document"],
|
||||
with_group_users=False,
|
||||
).values_list("id", flat=True),
|
||||
)
|
||||
overrides.custom_fields = {
|
||||
|
||||
@@ -3,7 +3,6 @@ from itertools import islice
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
from typing import Final
|
||||
|
||||
from allauth.mfa.models import Authenticator
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
@@ -71,15 +70,6 @@ from paperless.models import ApplicationConfiguration
|
||||
from paperless_mail.models import MailAccount
|
||||
from paperless_mail.models import MailRule
|
||||
|
||||
# Derived names replace the original's extension with these suffixes
|
||||
THUMBNAIL_SUFFIX: Final[str] = "-thumbnail.webp"
|
||||
ARCHIVE_SUFFIX: Final[str] = "-archive.pdf"
|
||||
# Leave room for the longest derived suffix, which replaces the original's extension
|
||||
MAX_BASE_NAME_BYTES: Final[int] = Document.MAX_PUBLIC_FILENAME_BYTES - max(
|
||||
len(THUMBNAIL_SUFFIX),
|
||||
len(ARCHIVE_SUFFIX),
|
||||
)
|
||||
|
||||
|
||||
def serialize_queryset_batched(
|
||||
queryset: "QuerySet[Any]",
|
||||
@@ -522,10 +512,7 @@ class Command(CryptMixin, PaperlessCommand):
|
||||
counter=filename_counter,
|
||||
)
|
||||
else:
|
||||
base_name = document.get_public_filename(
|
||||
counter=filename_counter,
|
||||
max_bytes=MAX_BASE_NAME_BYTES,
|
||||
)
|
||||
base_name = document.get_public_filename(counter=filename_counter)
|
||||
|
||||
# The thumbnail and archive names are derived from the stem, so
|
||||
# uniqueness must hold without the original's extension
|
||||
@@ -554,7 +541,7 @@ class Command(CryptMixin, PaperlessCommand):
|
||||
document_dict[EXPORTER_FILE_NAME] = original_arc
|
||||
|
||||
if not self.no_thumbnail:
|
||||
thumbnail_name = base_name.parent / (base_name.stem + THUMBNAIL_SUFFIX)
|
||||
thumbnail_name = base_name.parent / (base_name.stem + "-thumbnail.webp")
|
||||
if self.use_folder_prefix:
|
||||
thumbnail_name = Path("thumbnails") / thumbnail_name
|
||||
thumbnail_arc = thumbnail_name.as_posix()
|
||||
@@ -563,7 +550,7 @@ class Command(CryptMixin, PaperlessCommand):
|
||||
thumbnail_arc = None
|
||||
|
||||
if not self.no_archive and document.has_archive_version:
|
||||
archive_name = base_name.parent / (base_name.stem + ARCHIVE_SUFFIX)
|
||||
archive_name = base_name.parent / (base_name.stem + "-archive.pdf")
|
||||
if self.use_folder_prefix:
|
||||
archive_name = Path("archive") / archive_name
|
||||
archive_arc = archive_name.as_posix()
|
||||
|
||||
@@ -66,7 +66,7 @@ def iter_manifest_records(path: Path) -> Generator[dict, None, None]:
|
||||
"""Yield records one at a time from a manifest JSON array via ijson."""
|
||||
try:
|
||||
with path.open("rb") as f:
|
||||
yield from ijson.items(f, "item", use_float=True)
|
||||
yield from ijson.items(f, "item")
|
||||
except ijson.JSONError as e:
|
||||
raise CommandError(f"Failed to parse manifest file {path}: {e}") from e
|
||||
|
||||
|
||||
+12
-23
@@ -157,8 +157,6 @@ class StoragePath(MatchingModel):
|
||||
|
||||
class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-missing]
|
||||
MAX_STORED_FILENAME_LENGTH: Final[int] = 1024
|
||||
# Per-component limit of common filesystems (ext4, XFS, btrfs, ZFS, APFS)
|
||||
MAX_PUBLIC_FILENAME_BYTES: Final[int] = 255
|
||||
|
||||
correspondent = models.ForeignKey(
|
||||
Correspondent,
|
||||
@@ -486,37 +484,28 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
|
||||
def archive_file(self):
|
||||
return Path(self.archive_path).open("rb")
|
||||
|
||||
def get_public_filename(
|
||||
self,
|
||||
*,
|
||||
archive: bool = False,
|
||||
counter: int = 0,
|
||||
max_bytes: int = MAX_PUBLIC_FILENAME_BYTES,
|
||||
) -> str:
|
||||
def get_public_filename(self, *, archive=False, counter=0, suffix=None) -> str:
|
||||
"""
|
||||
Returns a sanitized filename for the document, not including any paths.
|
||||
|
||||
The name fits in max_bytes (UTF-8). A long title is shortened before
|
||||
the counter and extension are appended, because sanitize_filename
|
||||
truncates from the end. The tail is cut only when max_bytes is
|
||||
smaller than the tail itself.
|
||||
"""
|
||||
# Root owns metadata for all versions
|
||||
context_document = (
|
||||
self.root_document if self.root_document_id is not None else self
|
||||
)
|
||||
result = str(context_document)
|
||||
|
||||
extension = ".pdf" if archive else self.file_type
|
||||
tail = f"_{counter:02}{extension}" if counter else extension
|
||||
if counter:
|
||||
result += f"_{counter:02}"
|
||||
|
||||
head_bytes = max(0, max_bytes - len(tail.encode()))
|
||||
head = str(context_document).encode()[:head_bytes].decode(errors="ignore")
|
||||
if suffix:
|
||||
result += suffix
|
||||
|
||||
return pathvalidate.sanitize_filename(
|
||||
head + tail,
|
||||
replacement_text="-",
|
||||
max_len=max_bytes,
|
||||
)
|
||||
if archive:
|
||||
result += ".pdf"
|
||||
else:
|
||||
result += self.file_type
|
||||
|
||||
return pathvalidate.sanitize_filename(result, replacement_text="-")
|
||||
|
||||
@property
|
||||
def file_type(self):
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Pure PDF page operations used by documents.bulk_edit.
|
||||
|
||||
This module deliberately knows nothing about Django, Celery or the documents
|
||||
app: callers resolve documents, choose output paths and queue work. Every
|
||||
function that writes a PDF removes unreferenced resources before saving.
|
||||
|
||||
pikepdf is always called as ``pikepdf.open(...)`` / ``pikepdf.new()`` (never
|
||||
``from pikepdf import open``) so tests can patch those module attributes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import NamedTuple
|
||||
|
||||
import pikepdf
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
|
||||
|
||||
class PageSpec(NamedTuple):
|
||||
"""One page of an output PDF: a 1-indexed source page, optionally rotated."""
|
||||
|
||||
page: int
|
||||
rotate: int = 0 # relative degrees, 0 leaves the page alone
|
||||
|
||||
|
||||
def _require_positive(pages: Iterable[int]) -> None:
|
||||
for page in pages:
|
||||
if page < 1:
|
||||
raise ValueError(f"Page numbers start at 1, got {page}")
|
||||
|
||||
|
||||
def rotate_pdf(src: Path, dst: Path, degrees: int) -> None:
|
||||
"""
|
||||
Rotate every page relatively on the opened document, not a rebuild, so Info,
|
||||
XMP and outlines are kept. ``src`` is not modified.
|
||||
"""
|
||||
with pikepdf.open(src) as pdf:
|
||||
for page in pdf.pages:
|
||||
page.rotate(degrees, relative=True)
|
||||
pdf.remove_unreferenced_resources()
|
||||
pdf.save(dst)
|
||||
|
||||
|
||||
def remove_pages(src: Path, dst: Path, pages: Iterable[int]) -> None:
|
||||
"""
|
||||
Remove 1-indexed pages from the opened document, not a rebuild, so Info, XMP
|
||||
and outlines are kept. ``src`` is not modified.
|
||||
|
||||
Duplicates are ignored. Pages are removed highest first so earlier removals
|
||||
never shift the index of later ones.
|
||||
"""
|
||||
unique = sorted(set(pages))
|
||||
_require_positive(unique)
|
||||
with pikepdf.open(src) as pdf:
|
||||
for page_num in reversed(unique):
|
||||
del pdf.pages[page_num - 1]
|
||||
pdf.remove_unreferenced_resources()
|
||||
pdf.save(dst)
|
||||
|
||||
|
||||
def build_pdfs(
|
||||
src: Path,
|
||||
outputs: Sequence[tuple[Sequence[PageSpec], Callable[[], Path]]],
|
||||
) -> list[Path]:
|
||||
"""
|
||||
Build one new PDF per output from pages of ``src``, opening ``src`` once.
|
||||
|
||||
Each output is ``(page_specs, make_dst)``. Every page number is checked against
|
||||
``src`` before any output is built, and ``make_dst`` is called after that
|
||||
output's pages are copied and immediately before it is saved, so a bad page
|
||||
number in any output never leaves a destination behind. Document-level data
|
||||
(Info, XMP, outlines) is not carried over. Returns the written paths in output
|
||||
order.
|
||||
"""
|
||||
for specs, _ in outputs:
|
||||
_require_positive(spec.page for spec in specs)
|
||||
|
||||
written: list[Path] = []
|
||||
with pikepdf.open(src) as source:
|
||||
page_count = len(source.pages)
|
||||
for specs, _ in outputs:
|
||||
for spec in specs:
|
||||
if spec.page > page_count:
|
||||
raise IndexError(
|
||||
f"Page {spec.page} is out of range, the PDF has "
|
||||
f"{page_count} pages",
|
||||
)
|
||||
for specs, make_dst in outputs:
|
||||
dst = pikepdf.new()
|
||||
for spec in specs:
|
||||
dst.pages.append(source.pages[spec.page - 1])
|
||||
if spec.rotate:
|
||||
dst.pages[-1].rotate(spec.rotate, relative=True)
|
||||
dst.remove_unreferenced_resources()
|
||||
path = make_dst()
|
||||
dst.save(path)
|
||||
dst.close()
|
||||
written.append(path)
|
||||
return written
|
||||
|
||||
|
||||
def validate_page_operations(
|
||||
operations: Sequence[Mapping[str, int]],
|
||||
*,
|
||||
single_output: bool,
|
||||
) -> int:
|
||||
"""
|
||||
Validate ``edit_pdf`` style operations and return the output document count.
|
||||
|
||||
Each operation has ``page`` and optionally ``rotate`` and ``doc`` (the output
|
||||
document index, default 0). The bounds rule is kept as it was: a ``doc`` index
|
||||
must be below the number of operations.
|
||||
"""
|
||||
if not operations:
|
||||
raise ValueError("Output document index is out of bounds")
|
||||
|
||||
max_idx = max(op.get("doc", 0) for op in operations)
|
||||
if single_output and max_idx > 0:
|
||||
raise ValueError("Multiple output documents specified")
|
||||
|
||||
if any(
|
||||
op.get("doc", 0) < 0 or op.get("doc", 0) >= len(operations) for op in operations
|
||||
):
|
||||
raise ValueError("Output document index is out of bounds")
|
||||
|
||||
return max_idx + 1
|
||||
|
||||
|
||||
def needs_decrypt(src: Path) -> bool:
|
||||
"""
|
||||
True if ``src`` is encrypted. A PDF that needs a password to open at all
|
||||
counts as encrypted.
|
||||
"""
|
||||
try:
|
||||
with pikepdf.open(src) as pdf:
|
||||
return bool(pdf.is_encrypted)
|
||||
except pikepdf.PasswordError:
|
||||
return True
|
||||
|
||||
|
||||
def decrypt_pdf(src: Path, make_dst: Callable[[], Path], password: str) -> Path:
|
||||
"""
|
||||
Write an unencrypted copy of ``src`` and return its path.
|
||||
|
||||
``make_dst`` is only called once the password has been accepted, so a wrong
|
||||
password never leaves a destination behind.
|
||||
"""
|
||||
with pikepdf.open(src, password=password) as pdf:
|
||||
pdf.remove_unreferenced_resources()
|
||||
dst = make_dst()
|
||||
pdf.save(dst)
|
||||
return dst
|
||||
|
||||
|
||||
class PdfMerger:
|
||||
"""
|
||||
Accumulates the pages of several PDFs into one new PDF.
|
||||
|
||||
``add`` raises if a source cannot be read; deciding whether to skip it is the
|
||||
caller's policy. Use as a context manager so the merged PDF is closed.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._pdf = pikepdf.new()
|
||||
self._version: str = self._pdf.pdf_version
|
||||
|
||||
def __enter__(self) -> PdfMerger:
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc: BaseException | None,
|
||||
tb: TracebackType | None,
|
||||
) -> None:
|
||||
self._pdf.close()
|
||||
|
||||
def add(self, path: Path) -> None:
|
||||
with pikepdf.open(str(path)) as pdf:
|
||||
self._version = max(self._version, pdf.pdf_version)
|
||||
self._pdf.pages.extend(pdf.pages)
|
||||
|
||||
def save(self, dst: Path) -> None:
|
||||
self._pdf.remove_unreferenced_resources()
|
||||
self._pdf.save(dst, min_version=self._version)
|
||||
@@ -511,28 +511,6 @@ def restrict_queryset_to_visible(
|
||||
return queryset.filter(pk__in=permitted_object_ids(user, queryset.model, perm))
|
||||
|
||||
|
||||
def user_is_active_superuser(user: User | None) -> bool:
|
||||
"""
|
||||
True only for an active, authenticated superuser.
|
||||
"""
|
||||
return user is not None and user_is_unrestricted(user)
|
||||
|
||||
|
||||
def restrict_queryset_to_owner_visible(
|
||||
queryset: QuerySet[ModelT],
|
||||
user: User | None,
|
||||
perm: str,
|
||||
) -> QuerySet[ModelT]:
|
||||
"""
|
||||
Like ``restrict_queryset_to_visible``, but ``user=None`` sees only unowned
|
||||
rows (``permitted_object_ids(None, ...)`` semantics) instead of every row.
|
||||
See ``user_is_active_superuser``.
|
||||
"""
|
||||
if user_is_active_superuser(user):
|
||||
return queryset
|
||||
return queryset.filter(pk__in=permitted_object_ids(user, queryset.model, perm))
|
||||
|
||||
|
||||
def permitted_document_ids(
|
||||
user: User | None,
|
||||
*,
|
||||
|
||||
@@ -926,7 +926,6 @@ class TantivyBackend:
|
||||
sort_field: str | None = None,
|
||||
sort_reverse: bool = False,
|
||||
search_mode: SearchMode = SearchMode.QUERY,
|
||||
filter_query: str | None = None,
|
||||
limit: int | None = None,
|
||||
) -> list[int]:
|
||||
"""
|
||||
@@ -942,7 +941,6 @@ class TantivyBackend:
|
||||
sort_field: Field to sort by (None for relevance ranking)
|
||||
sort_reverse: Whether to reverse the sort order
|
||||
search_mode: Query parsing mode (QUERY, TEXT, or TITLE)
|
||||
filter_query: Optional advanced query results must also match
|
||||
limit: Maximum number of IDs to return (None = all matching docs)
|
||||
|
||||
Returns:
|
||||
@@ -950,13 +948,6 @@ class TantivyBackend:
|
||||
"""
|
||||
self._ensure_open()
|
||||
user_query = self._parse_query(query, search_mode)
|
||||
if filter_query is not None:
|
||||
user_query = user_query.and_must_match(
|
||||
tantivy.Query.const_score_query(
|
||||
self._parse_query(filter_query, SearchMode.QUERY),
|
||||
0.0, # zero so doesn't affect relevance
|
||||
),
|
||||
)
|
||||
final_query = self._apply_permission_filter(user_query, user)
|
||||
|
||||
searcher = self._index.searcher()
|
||||
|
||||
@@ -2144,6 +2144,8 @@ class BulkEditSerializer(
|
||||
raise serializers.ValidationError("pages must be a list")
|
||||
if not all(isinstance(i, int) for i in parameters["pages"]):
|
||||
raise serializers.ValidationError("pages must be a list of integers")
|
||||
if any(i < 1 for i in parameters["pages"]):
|
||||
raise serializers.ValidationError("pages must be positive integers")
|
||||
|
||||
def _validate_parameters_merge(self, parameters) -> None:
|
||||
if "delete_originals" in parameters:
|
||||
|
||||
@@ -835,7 +835,6 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
||||
for filters in (
|
||||
{"text": "new doc 2017-03-16"},
|
||||
{"title_search": "apple"},
|
||||
{"title_search": "apple", "query": "created:[-1 year to now]"},
|
||||
):
|
||||
with self.subTest(filters=filters):
|
||||
get_backend.return_value.search_ids.return_value = [self.doc2.id]
|
||||
@@ -887,7 +886,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
||||
"all": True,
|
||||
"filters": {
|
||||
"text": "B",
|
||||
"title_search": "c1",
|
||||
"query": "c1",
|
||||
},
|
||||
"method": "set_storage_path",
|
||||
"parameters": {"storage_path": self.sp1.id},
|
||||
@@ -1844,6 +1843,36 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
||||
m.assert_called_once()
|
||||
self.assertEqual(m.call_args.kwargs["pages"], [[1], [2, 3, 4], [5]])
|
||||
|
||||
@mock.patch("documents.serialisers.bulk_edit.delete_pages")
|
||||
def test_bulk_edit_delete_pages_rejects_pages_below_one(self, m) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A legacy delete_pages bulk edit
|
||||
WHEN:
|
||||
- API to bulk edit is called with a page number below 1
|
||||
THEN:
|
||||
- API returns HTTP 400
|
||||
- delete_pages is not called
|
||||
"""
|
||||
self.setup_mock(m, "delete_pages")
|
||||
|
||||
for pages in ([0], [-1], [1, 0]):
|
||||
with self.subTest(pages=pages):
|
||||
response = self.client.post(
|
||||
"/api/documents/bulk_edit/",
|
||||
json.dumps(
|
||||
{
|
||||
"documents": [self.doc2.id],
|
||||
"method": "delete_pages",
|
||||
"parameters": {"pages": pages},
|
||||
},
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn(b"pages must be positive integers", response.content)
|
||||
m.assert_not_called()
|
||||
|
||||
@mock.patch("documents.views.bulk_edit.rotate")
|
||||
def test_rotate_insufficient_permissions(self, m) -> None:
|
||||
self.doc1.owner = User.objects.get(username="temp_admin")
|
||||
|
||||
@@ -245,68 +245,8 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(response.data["count"], 1)
|
||||
self.assertEqual(response.data["results"][0]["id"], title_match.id)
|
||||
|
||||
def test_simple_search_combined_with_query_filter(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A document with the term in its title, created this year
|
||||
- A document with the term only in its content, created this year
|
||||
- A document with the term in its title, created years ago
|
||||
WHEN:
|
||||
- A title or text search is combined with a relative created
|
||||
date query, as the filter editor sends it
|
||||
THEN:
|
||||
- The simple search keeps its own field scope and the query only
|
||||
narrows the results (issue #14436)
|
||||
"""
|
||||
this_year = timezone.localdate().replace(month=1, day=1)
|
||||
title_match = Document.objects.create(
|
||||
title="foo report",
|
||||
content="nothing relevant",
|
||||
checksum="F1",
|
||||
created=this_year,
|
||||
pk=21,
|
||||
)
|
||||
content_match = Document.objects.create(
|
||||
title="something else",
|
||||
content="foo appears here",
|
||||
checksum="F2",
|
||||
created=this_year,
|
||||
pk=22,
|
||||
)
|
||||
old_title_match = Document.objects.create(
|
||||
title="foo archive",
|
||||
content="nothing relevant",
|
||||
checksum="F3",
|
||||
created=datetime.date(2001, 1, 1),
|
||||
pk=23,
|
||||
)
|
||||
backend = get_backend()
|
||||
for doc in (title_match, content_match, old_title_match):
|
||||
backend.add_or_update(doc)
|
||||
|
||||
query = "created:[-1 year to now]"
|
||||
response = self.client.get(
|
||||
"/api/documents/",
|
||||
{"title_search": "foo", "query": query},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
[r["id"] for r in response.data["results"]],
|
||||
[title_match.id],
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
"/api/documents/",
|
||||
{"text": "foo", "query": query},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertCountEqual(
|
||||
[r["id"] for r in response.data["results"]],
|
||||
[title_match.id, content_match.id],
|
||||
)
|
||||
|
||||
def test_search_rejects_multiple_search_modes(self) -> None:
|
||||
response = self.client.get("/api/documents/?text=bank&title_search=bank")
|
||||
response = self.client.get("/api/documents/?text=bank&query=bank")
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
response.data["detail"],
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import shutil
|
||||
from collections.abc import Callable
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pikepdf
|
||||
import pytest
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.models import Permission
|
||||
@@ -23,6 +23,7 @@ from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.pdf_ops import PageSpec
|
||||
from documents.permissions import set_permissions_for_objects
|
||||
from paperless_testing.dirs import DirectoriesMixin
|
||||
from paperless_testing.factories import DocumentFactory
|
||||
@@ -796,16 +797,14 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
self.img_doc.save()
|
||||
|
||||
@staticmethod
|
||||
def mock_password_required_pdf(
|
||||
mock_open: mock.Mock,
|
||||
fake_pdf: mock.Mock,
|
||||
) -> None:
|
||||
password_context = mock.MagicMock()
|
||||
password_context.__enter__.return_value = fake_pdf
|
||||
mock_open.side_effect = [
|
||||
pikepdf.PasswordError("password required"),
|
||||
password_context,
|
||||
]
|
||||
def fake_decrypt(
|
||||
src: Path,
|
||||
make_dst: Callable[[], Path],
|
||||
password: str,
|
||||
) -> Path:
|
||||
dst = make_dst()
|
||||
dst.write_bytes(b"password removed")
|
||||
return dst
|
||||
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
def test_merge(self, mock_consume_file) -> None:
|
||||
@@ -850,12 +849,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.PdfMerger")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
def test_merge_uses_latest_version_source_for_root_selection(
|
||||
self,
|
||||
mock_consume_file,
|
||||
mock_open_pdf,
|
||||
mock_merger,
|
||||
) -> None:
|
||||
version_file = self.dirs.scratch_dir / "sample2_version_merge.pdf"
|
||||
shutil.copy(self.doc2.source_path, version_file)
|
||||
@@ -866,16 +865,14 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
filename=version_file,
|
||||
mime_type="application/pdf",
|
||||
)
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.pdf_version = "1.7"
|
||||
fake_pdf.pages = [mock.Mock()]
|
||||
mock_open_pdf.return_value.__enter__.return_value = fake_pdf
|
||||
merger = mock_merger.return_value.__enter__.return_value
|
||||
merger.save.side_effect = lambda dst: shutil.copy(version.source_path, dst)
|
||||
|
||||
result = bulk_edit.merge([self.doc2.id])
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
mock_open_pdf.assert_called_once_with(str(version.source_path))
|
||||
mock_consume_file.assert_not_called()
|
||||
merger.add.assert_called_once_with(version.source_path)
|
||||
mock_consume_file.assert_called_once()
|
||||
|
||||
@mock.patch("documents.bulk_edit.delete.si")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
@@ -1037,18 +1034,18 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
@mock.patch("documents.tasks.consume_file.delay")
|
||||
@mock.patch("pikepdf.open")
|
||||
def test_merge_with_errors(self, mock_open_pdf, mock_consume_file) -> None:
|
||||
@mock.patch("documents.pdf_ops.PdfMerger.add")
|
||||
def test_merge_with_errors(self, mock_add, mock_consume_file) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing documents
|
||||
WHEN:
|
||||
- Merge action is called with 2 documents
|
||||
- Error occurs when opening both files
|
||||
- Error occurs when adding both files
|
||||
THEN:
|
||||
- Consume file should not be called
|
||||
"""
|
||||
mock_open_pdf.side_effect = Exception("Error opening PDF")
|
||||
mock_add.side_effect = Exception("Error opening PDF")
|
||||
doc_ids = [self.doc2.id, self.doc3.id]
|
||||
|
||||
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
||||
@@ -1085,12 +1082,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(result, "OK")
|
||||
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.build_pdfs")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
def test_split_uses_latest_version_source_for_root_selection(
|
||||
self,
|
||||
mock_consume_file,
|
||||
mock_open_pdf,
|
||||
mock_build_pdfs,
|
||||
mock_group,
|
||||
) -> None:
|
||||
version_file = self.dirs.scratch_dir / "sample2_version_split.pdf"
|
||||
@@ -1102,17 +1099,15 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
filename=version_file,
|
||||
mime_type="application/pdf",
|
||||
)
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.pages = [mock.Mock(), mock.Mock()]
|
||||
mock_open_pdf.return_value.__enter__.return_value = fake_pdf
|
||||
mock_build_pdfs.return_value = [version.source_path, version.source_path]
|
||||
mock_group.return_value.delay.return_value = None
|
||||
|
||||
result = bulk_edit.split([self.doc2.id], [[1], [2]])
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
mock_open_pdf.assert_called_once_with(version.source_path)
|
||||
mock_consume_file.assert_not_called()
|
||||
mock_group.return_value.delay.assert_not_called()
|
||||
self.assertEqual(mock_build_pdfs.call_args.args[0], version.source_path)
|
||||
self.assertEqual(mock_consume_file.call_count, 2)
|
||||
mock_group.return_value.delay.assert_called_once()
|
||||
|
||||
@mock.patch("documents.bulk_edit.delete.si")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
@@ -1200,18 +1195,18 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(self.doc2.archive_serial_number, 222)
|
||||
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("pikepdf.Pdf.save")
|
||||
def test_split_with_errors(self, mock_save_pdf, mock_consume_file) -> None:
|
||||
@mock.patch("documents.pdf_ops.build_pdfs")
|
||||
def test_split_with_errors(self, mock_build_pdfs, mock_consume_file) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing documents
|
||||
WHEN:
|
||||
- Split action is called with 1 document and 2 page groups
|
||||
- Error occurs when saving the files
|
||||
- Error occurs when building the files
|
||||
THEN:
|
||||
- Consume file should not be called
|
||||
"""
|
||||
mock_save_pdf.side_effect = Exception("Error saving PDF")
|
||||
mock_build_pdfs.side_effect = Exception("Error building PDFs")
|
||||
doc_ids = [self.doc2.id]
|
||||
pages = [[1, 2], [3]]
|
||||
|
||||
@@ -1246,10 +1241,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(result, "OK")
|
||||
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("pikepdf.Pdf.save")
|
||||
@mock.patch("documents.pdf_ops.rotate_pdf")
|
||||
def test_rotate_with_error(
|
||||
self,
|
||||
mock_pdf_save,
|
||||
mock_rotate_pdf,
|
||||
mock_consume_delay,
|
||||
) -> None:
|
||||
"""
|
||||
@@ -1257,11 +1252,11 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
- Existing documents
|
||||
WHEN:
|
||||
- Rotate action is called with 2 documents
|
||||
- PikePDF raises an error
|
||||
- Rotating the PDF raises an error
|
||||
THEN:
|
||||
- Rotate action should be called 0 times
|
||||
"""
|
||||
mock_pdf_save.side_effect = Exception("Error saving PDF")
|
||||
mock_rotate_pdf.side_effect = Exception("Error rotating PDF")
|
||||
doc_ids = [self.doc2.id, self.doc3.id]
|
||||
|
||||
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
||||
@@ -1296,10 +1291,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
|
||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.rotate_pdf")
|
||||
def test_rotate_explicit_selection_uses_root_source_when_root_selected(
|
||||
self,
|
||||
mock_open,
|
||||
mock_rotate_pdf,
|
||||
mock_consume_delay,
|
||||
mock_magic,
|
||||
) -> None:
|
||||
@@ -1308,9 +1303,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
title="B version 1",
|
||||
root_document=self.doc2,
|
||||
)
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.pages = [mock.Mock()]
|
||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
||||
|
||||
result = bulk_edit.rotate(
|
||||
[self.doc2.id],
|
||||
@@ -1319,26 +1311,35 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
mock_open.assert_called_once_with(self.doc2.source_path)
|
||||
self.assertEqual(mock_rotate_pdf.call_args.args[0], self.doc2.source_path)
|
||||
mock_consume_delay.assert_called_once()
|
||||
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("pikepdf.Pdf.save")
|
||||
@mock.patch("documents.pdf_ops.remove_pages")
|
||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||
def test_delete_pages(self, mock_magic, mock_pdf_save, mock_consume_delay) -> None:
|
||||
def test_delete_pages(
|
||||
self,
|
||||
mock_magic,
|
||||
mock_remove_pages,
|
||||
mock_consume_delay,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing documents
|
||||
WHEN:
|
||||
- Delete pages action is called with 1 document and 2 pages
|
||||
THEN:
|
||||
- Save should be called once
|
||||
- The pages are removed from the document's source PDF
|
||||
- A new version should be enqueued via consume_file
|
||||
"""
|
||||
doc_ids = [self.doc2.id]
|
||||
pages = [1, 3]
|
||||
result = bulk_edit.delete_pages(doc_ids, pages)
|
||||
mock_pdf_save.assert_called_once()
|
||||
mock_remove_pages.assert_called_once_with(
|
||||
self.doc2.source_path,
|
||||
mock.ANY,
|
||||
[1, 3],
|
||||
)
|
||||
mock_consume_delay.assert_called_once()
|
||||
task_kwargs = mock_consume_delay.call_args.kwargs["kwargs"]
|
||||
self.assertEqual(task_kwargs["input_doc"].root_document_id, self.doc2.id)
|
||||
@@ -1350,10 +1351,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
|
||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.remove_pages")
|
||||
def test_delete_pages_explicit_selection_uses_root_source_when_root_selected(
|
||||
self,
|
||||
mock_open,
|
||||
mock_remove_pages,
|
||||
mock_consume_delay,
|
||||
mock_magic,
|
||||
) -> None:
|
||||
@@ -1362,9 +1363,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
title="B version 1",
|
||||
root_document=self.doc2,
|
||||
)
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.pages = [mock.Mock(), mock.Mock()]
|
||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
||||
|
||||
result = bulk_edit.delete_pages(
|
||||
[self.doc2.id],
|
||||
@@ -1373,23 +1371,26 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
mock_open.assert_called_once_with(self.doc2.source_path)
|
||||
self.assertEqual(mock_remove_pages.call_args.args[0], self.doc2.source_path)
|
||||
mock_consume_delay.assert_called_once()
|
||||
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("pikepdf.Pdf.save")
|
||||
def test_delete_pages_with_error(self, mock_pdf_save, mock_consume_delay) -> None:
|
||||
@mock.patch("documents.pdf_ops.remove_pages")
|
||||
def test_delete_pages_with_error(
|
||||
self,
|
||||
mock_remove_pages,
|
||||
mock_consume_delay,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing documents
|
||||
WHEN:
|
||||
- Delete pages action is called with 1 document and 2 pages
|
||||
- PikePDF raises an error
|
||||
- Removing the pages raises an error
|
||||
THEN:
|
||||
- Save should be called once
|
||||
- No new version should be enqueued
|
||||
"""
|
||||
mock_pdf_save.side_effect = Exception("Error saving PDF")
|
||||
mock_remove_pages.side_effect = Exception("Error removing pages")
|
||||
doc_ids = [self.doc2.id]
|
||||
pages = [1, 3]
|
||||
|
||||
@@ -1419,6 +1420,41 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(result, "OK")
|
||||
mock_group.return_value.delay.assert_called_once()
|
||||
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@mock.patch("documents.pdf_ops.build_pdfs")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
def test_edit_pdf_maps_operations_to_outputs(
|
||||
self,
|
||||
mock_consume_file: mock.Mock,
|
||||
mock_build_pdfs: mock.Mock,
|
||||
mock_group: mock.Mock,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing document
|
||||
WHEN:
|
||||
- edit_pdf is called with operations interleaved across two outputs,
|
||||
some of them rotated
|
||||
THEN:
|
||||
- Each output is built from its own operations, in operation order,
|
||||
with the requested rotation
|
||||
"""
|
||||
mock_build_pdfs.return_value = [self.doc2.source_path, self.doc2.source_path]
|
||||
mock_group.return_value.delay.return_value = None
|
||||
operations = [
|
||||
{"page": 3, "doc": 1},
|
||||
{"page": 1, "doc": 0, "rotate": 90},
|
||||
{"page": 2, "doc": 1, "rotate": 180},
|
||||
]
|
||||
|
||||
bulk_edit.edit_pdf([self.doc2.id], operations)
|
||||
|
||||
outputs = mock_build_pdfs.call_args.args[1]
|
||||
self.assertEqual(
|
||||
[specs for specs, _ in outputs],
|
||||
[[PageSpec(1, 90)], [PageSpec(3), PageSpec(2, 180)]],
|
||||
)
|
||||
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
def test_edit_pdf_with_user_override(self, mock_consume_file, mock_group) -> None:
|
||||
@@ -1534,12 +1570,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
|
||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("pikepdf.new")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.build_pdfs")
|
||||
def test_edit_pdf_explicit_selection_uses_root_source_when_root_selected(
|
||||
self,
|
||||
mock_open,
|
||||
mock_new,
|
||||
mock_build_pdfs,
|
||||
mock_consume_delay,
|
||||
mock_magic,
|
||||
) -> None:
|
||||
@@ -1548,12 +1582,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
title="B version 1",
|
||||
root_document=self.doc2,
|
||||
)
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.pages = [mock.Mock()]
|
||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
||||
output_pdf = mock.MagicMock()
|
||||
output_pdf.pages = []
|
||||
mock_new.return_value = output_pdf
|
||||
mock_build_pdfs.return_value = [Path("edited.pdf")]
|
||||
|
||||
result = bulk_edit.edit_pdf(
|
||||
[self.doc2.id],
|
||||
@@ -1563,7 +1592,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
mock_open.assert_called_once_with(self.doc2.source_path)
|
||||
self.assertEqual(mock_build_pdfs.call_args.args[0], self.doc2.source_path)
|
||||
mock_consume_delay.assert_called_once()
|
||||
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@@ -1589,31 +1618,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(result, "OK")
|
||||
mock_group.return_value.delay.assert_called_once()
|
||||
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
def test_edit_pdf_open_failure(
|
||||
self,
|
||||
mock_consume_file: mock.Mock,
|
||||
mock_group: mock.Mock,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing document
|
||||
WHEN:
|
||||
- edit_pdf fails to open PDF
|
||||
THEN:
|
||||
- Task group is not called
|
||||
"""
|
||||
doc_ids = [self.doc2.id]
|
||||
operations = [
|
||||
{"page": 9999}, # invalid page, forces error during PDF load
|
||||
]
|
||||
with self.assertLogs("paperless.bulk_edit", level="ERROR"):
|
||||
with self.assertRaises(Exception):
|
||||
bulk_edit.edit_pdf(doc_ids, operations)
|
||||
mock_group.assert_not_called()
|
||||
mock_consume_file.assert_not_called()
|
||||
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
def test_edit_pdf_multiple_outputs_with_update_flag_errors(
|
||||
@@ -1640,23 +1644,15 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
mock_group.assert_not_called()
|
||||
mock_consume_file.assert_not_called()
|
||||
|
||||
@mock.patch("pikepdf.open")
|
||||
def test_edit_pdf_rejects_invalid_operations(self, mock_open) -> None:
|
||||
for operations in ([], [{"page": 1, "doc": 2**32}]):
|
||||
with self.subTest(operations=operations):
|
||||
with self.assertLogs("paperless.bulk_edit", level="ERROR"):
|
||||
with self.assertRaisesRegex(ValueError, "index is out of bounds"):
|
||||
bulk_edit.edit_pdf([self.doc2.id], operations)
|
||||
|
||||
mock_open.assert_not_called()
|
||||
|
||||
@mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file.delay")
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||
def test_remove_password_update_document(
|
||||
self,
|
||||
mock_open,
|
||||
mock_needs_decrypt,
|
||||
mock_decrypt,
|
||||
mock_mkdtemp,
|
||||
mock_consume_delay,
|
||||
mock_update_document,
|
||||
@@ -1665,16 +1661,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
temp_dir = self.dirs.scratch_dir / "remove-password-update"
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
mock_mkdtemp.return_value = str(temp_dir)
|
||||
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.pages = [mock.Mock(), mock.Mock(), mock.Mock()]
|
||||
fake_pdf.is_encrypted = True
|
||||
|
||||
def save_side_effect(target_path):
|
||||
Path(target_path).write_bytes(b"new pdf content")
|
||||
|
||||
fake_pdf.save.side_effect = save_side_effect
|
||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
||||
mock_decrypt.side_effect = self.fake_decrypt
|
||||
|
||||
result = bulk_edit.remove_password(
|
||||
[doc.id],
|
||||
@@ -1683,14 +1670,8 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
self.assertEqual(
|
||||
mock_open.call_args_list,
|
||||
[
|
||||
mock.call(doc.source_path),
|
||||
mock.call(doc.source_path, password="secret"),
|
||||
],
|
||||
)
|
||||
fake_pdf.remove_unreferenced_resources.assert_called_once()
|
||||
mock_needs_decrypt.assert_called_once_with(doc.source_path)
|
||||
mock_decrypt.assert_called_once_with(doc.source_path, mock.ANY, "secret")
|
||||
mock_update_document.assert_not_called()
|
||||
mock_consume_delay.assert_called_once()
|
||||
task_kwargs = mock_consume_delay.call_args.kwargs["kwargs"]
|
||||
@@ -1703,40 +1684,15 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(task_kwargs["input_doc"].root_document_id, doc.id)
|
||||
self.assertIsNotNone(task_kwargs["overrides"])
|
||||
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||
@mock.patch("pikepdf.open")
|
||||
def test_remove_password_update_document_skips_unencrypted_pdf(
|
||||
self,
|
||||
mock_open,
|
||||
mock_mkdtemp,
|
||||
mock_consume_delay,
|
||||
) -> None:
|
||||
doc = self.doc1
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.is_encrypted = False
|
||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
||||
|
||||
result = bulk_edit.remove_password(
|
||||
[doc.id],
|
||||
password="secret",
|
||||
update_document=True,
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
mock_open.assert_called_once_with(doc.source_path)
|
||||
fake_pdf.remove_unreferenced_resources.assert_not_called()
|
||||
fake_pdf.save.assert_not_called()
|
||||
mock_mkdtemp.assert_not_called()
|
||||
mock_consume_delay.assert_not_called()
|
||||
|
||||
@mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file.delay")
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||
def test_remove_password_update_document_uses_source_paths(
|
||||
self,
|
||||
mock_open,
|
||||
mock_needs_decrypt,
|
||||
mock_decrypt,
|
||||
mock_mkdtemp,
|
||||
mock_consume_delay,
|
||||
mock_update_document,
|
||||
@@ -1747,14 +1703,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
temp_dir = self.dirs.scratch_dir / "remove-password-source-file"
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
mock_mkdtemp.return_value = str(temp_dir)
|
||||
|
||||
fake_pdf = mock.MagicMock()
|
||||
self.mock_password_required_pdf(mock_open, fake_pdf)
|
||||
|
||||
def save_side_effect(target_path):
|
||||
Path(target_path).write_bytes(b"new pdf content")
|
||||
|
||||
fake_pdf.save.side_effect = save_side_effect
|
||||
mock_decrypt.side_effect = self.fake_decrypt
|
||||
|
||||
result = bulk_edit.remove_password(
|
||||
[doc.id],
|
||||
@@ -1764,22 +1713,19 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
self.assertEqual(
|
||||
mock_open.call_args_list,
|
||||
[
|
||||
mock.call(source_file),
|
||||
mock.call(source_file, password="secret"),
|
||||
],
|
||||
)
|
||||
mock_needs_decrypt.assert_called_once_with(source_file)
|
||||
mock_decrypt.assert_called_once_with(source_file, mock.ANY, "secret")
|
||||
mock_update_document.assert_not_called()
|
||||
mock_consume_delay.assert_called_once()
|
||||
|
||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||
def test_remove_password_explicit_selection_uses_root_source_when_root_selected(
|
||||
self,
|
||||
mock_open,
|
||||
mock_needs_decrypt,
|
||||
mock_decrypt,
|
||||
mock_consume_delay,
|
||||
mock_magic,
|
||||
) -> None:
|
||||
@@ -1788,8 +1734,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
title="A version 1",
|
||||
root_document=self.doc1,
|
||||
)
|
||||
fake_pdf = mock.MagicMock()
|
||||
self.mock_password_required_pdf(mock_open, fake_pdf)
|
||||
mock_decrypt.return_value = Path("unprotected.pdf")
|
||||
|
||||
result = bulk_edit.remove_password(
|
||||
[self.doc1.id],
|
||||
@@ -1799,12 +1744,11 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
self.assertEqual(
|
||||
mock_open.call_args_list,
|
||||
[
|
||||
mock.call(self.doc1.source_path),
|
||||
mock.call(self.doc1.source_path, password="secret"),
|
||||
],
|
||||
mock_needs_decrypt.assert_called_once_with(self.doc1.source_path)
|
||||
mock_decrypt.assert_called_once_with(
|
||||
self.doc1.source_path,
|
||||
mock.ANY,
|
||||
"secret",
|
||||
)
|
||||
mock_consume_delay.assert_called_once()
|
||||
|
||||
@@ -1812,10 +1756,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||
def test_remove_password_creates_consumable_document(
|
||||
self,
|
||||
mock_open: mock.Mock,
|
||||
mock_needs_decrypt: mock.Mock,
|
||||
mock_decrypt: mock.Mock,
|
||||
mock_mkdtemp: mock.Mock,
|
||||
mock_consume_file: mock.Mock,
|
||||
mock_group: mock.Mock,
|
||||
@@ -1825,15 +1771,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
temp_dir = self.dirs.scratch_dir / "remove-password"
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
mock_mkdtemp.return_value = str(temp_dir)
|
||||
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.pages = [mock.Mock(), mock.Mock()]
|
||||
self.mock_password_required_pdf(mock_open, fake_pdf)
|
||||
|
||||
def save_side_effect(target_path: Path) -> None:
|
||||
target_path.write_bytes(b"password removed")
|
||||
|
||||
fake_pdf.save.side_effect = save_side_effect
|
||||
mock_decrypt.side_effect = self.fake_decrypt
|
||||
mock_group.return_value.delay.return_value = None
|
||||
|
||||
user = User.objects.create(username="owner")
|
||||
@@ -1848,13 +1786,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
self.assertEqual(
|
||||
mock_open.call_args_list,
|
||||
[
|
||||
mock.call(doc.source_path),
|
||||
mock.call(doc.source_path, password="secret"),
|
||||
],
|
||||
)
|
||||
mock_decrypt.assert_called_once_with(doc.source_path, mock.ANY, "secret")
|
||||
mock_consume_file.assert_called_once()
|
||||
call_kwargs = mock_consume_file.call_args.kwargs
|
||||
consumable_document = call_kwargs["input_doc"]
|
||||
@@ -1876,21 +1808,18 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
@mock.patch("documents.bulk_edit.chord")
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=False)
|
||||
def test_remove_password_skips_unencrypted_pdf_without_queueing(
|
||||
self,
|
||||
mock_open: mock.Mock,
|
||||
mock_mkdtemp: mock.Mock,
|
||||
mock_needs_decrypt: mock.Mock,
|
||||
mock_decrypt: mock.Mock,
|
||||
mock_consume_file: mock.Mock,
|
||||
mock_group: mock.Mock,
|
||||
mock_chord: mock.Mock,
|
||||
mock_delete: mock.Mock,
|
||||
) -> None:
|
||||
doc = self.doc2
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.is_encrypted = False
|
||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
||||
|
||||
result = bulk_edit.remove_password(
|
||||
[doc.id],
|
||||
@@ -1900,10 +1829,8 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
mock_open.assert_called_once_with(doc.source_path)
|
||||
fake_pdf.remove_unreferenced_resources.assert_not_called()
|
||||
fake_pdf.save.assert_not_called()
|
||||
mock_mkdtemp.assert_not_called()
|
||||
mock_needs_decrypt.assert_called_once_with(doc.source_path)
|
||||
mock_decrypt.assert_not_called()
|
||||
mock_consume_file.assert_not_called()
|
||||
mock_group.assert_not_called()
|
||||
mock_chord.assert_not_called()
|
||||
@@ -1914,10 +1841,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
@mock.patch("documents.bulk_edit.group")
|
||||
@mock.patch("documents.tasks.consume_file.s")
|
||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||
@mock.patch("pikepdf.open")
|
||||
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||
def test_remove_password_deletes_original(
|
||||
self,
|
||||
mock_open: mock.Mock,
|
||||
mock_needs_decrypt: mock.Mock,
|
||||
mock_decrypt: mock.Mock,
|
||||
mock_mkdtemp: mock.Mock,
|
||||
mock_consume_file: mock.Mock,
|
||||
mock_group: mock.Mock,
|
||||
@@ -1928,15 +1857,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
temp_dir = self.dirs.scratch_dir / "remove-password-delete"
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
mock_mkdtemp.return_value = str(temp_dir)
|
||||
|
||||
fake_pdf = mock.MagicMock()
|
||||
fake_pdf.pages = [mock.Mock(), mock.Mock()]
|
||||
self.mock_password_required_pdf(mock_open, fake_pdf)
|
||||
|
||||
def save_side_effect(target_path: Path) -> None:
|
||||
target_path.write_bytes(b"password removed")
|
||||
|
||||
fake_pdf.save.side_effect = save_side_effect
|
||||
mock_decrypt.side_effect = self.fake_decrypt
|
||||
mock_chord.return_value.delay.return_value = None
|
||||
|
||||
result = bulk_edit.remove_password(
|
||||
@@ -1948,23 +1869,23 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(result, "OK")
|
||||
self.assertEqual(
|
||||
mock_open.call_args_list,
|
||||
[
|
||||
mock.call(doc.source_path),
|
||||
mock.call(doc.source_path, password="secret"),
|
||||
],
|
||||
)
|
||||
mock_decrypt.assert_called_once_with(doc.source_path, mock.ANY, "secret")
|
||||
mock_consume_file.assert_called_once()
|
||||
mock_group.assert_not_called()
|
||||
mock_chord.assert_called_once()
|
||||
mock_chord.return_value.delay.assert_called_once()
|
||||
mock_delete.si.assert_called_once_with([doc.id])
|
||||
|
||||
@mock.patch("pikepdf.open")
|
||||
def test_remove_password_open_failure(self, mock_open: mock.Mock) -> None:
|
||||
mock_open.side_effect = RuntimeError("wrong password")
|
||||
|
||||
@mock.patch(
|
||||
"documents.pdf_ops.decrypt_pdf",
|
||||
side_effect=RuntimeError("wrong password"),
|
||||
)
|
||||
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||
def test_remove_password_failure_raises_value_error(
|
||||
self,
|
||||
mock_needs_decrypt: mock.Mock,
|
||||
mock_decrypt: mock.Mock,
|
||||
) -> None:
|
||||
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
||||
with self.assertRaises(ValueError) as exc:
|
||||
bulk_edit.remove_password([self.doc1.id], password="secret")
|
||||
|
||||
@@ -14,7 +14,6 @@ from django.test import override_settings
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from pytest_django.fixtures import Settings
|
||||
from pytest_mock import MockerFixture
|
||||
from sklearn.neural_network import MLPClassifier
|
||||
|
||||
from documents.classifier import ClassifierModelCorruptError
|
||||
from documents.classifier import DocumentClassifier
|
||||
@@ -1037,49 +1036,6 @@ def test_classifier_match_threshold_default() -> None:
|
||||
assert settings.CLASSIFIER_MATCH_THRESHOLD == 0.3
|
||||
|
||||
|
||||
class TestPredictWithThresholdSingleClass:
|
||||
@pytest.fixture
|
||||
def single_class_model(self) -> tuple[MLPClassifier, np.ndarray]:
|
||||
X = np.random.default_rng(0).random((2, 5))
|
||||
return MLPClassifier(tol=0.01, random_state=0).fit(X, [3, 3]), X[:1]
|
||||
|
||||
def test_returns_only_class(
|
||||
self,
|
||||
single_class_model: tuple[MLPClassifier, np.ndarray],
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A classifier fitted on a single class (every document shares one label)
|
||||
WHEN:
|
||||
- A prediction is requested with the threshold disabled
|
||||
THEN:
|
||||
- No error is raised and the only class is returned
|
||||
"""
|
||||
classifier, X = single_class_model
|
||||
|
||||
assert _predict_with_threshold(classifier, X, threshold=0.0) == 3
|
||||
|
||||
def test_applies_threshold(
|
||||
self,
|
||||
single_class_model: tuple[MLPClassifier, np.ndarray],
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A classifier fitted on a single class
|
||||
WHEN:
|
||||
- A prediction is requested
|
||||
THEN:
|
||||
- The class's confidence is still compared against the threshold
|
||||
"""
|
||||
classifier, X = single_class_model
|
||||
confidence = classifier.predict_proba(X)[0][0]
|
||||
|
||||
assert _predict_with_threshold(classifier, X, threshold=confidence - 0.01) == 3
|
||||
assert (
|
||||
_predict_with_threshold(classifier, X, threshold=confidence + 0.01) is None
|
||||
)
|
||||
|
||||
|
||||
class TestPreprocessContent:
|
||||
@pytest.fixture
|
||||
def samples(self) -> Path:
|
||||
|
||||
@@ -34,10 +34,8 @@ from documents.tests.utils import GetConsumerMixin
|
||||
from paperless_mail.models import MailRule
|
||||
from paperless_testing.assertions import FileSystemAssertsMixin
|
||||
from paperless_testing.dirs import DirectoriesMixin
|
||||
from paperless_testing.factories import DocumentFactory
|
||||
from paperless_testing.factories import UserFactory
|
||||
from paperless_testing.fakes.progress import FakeProgressManager
|
||||
from paperless_testing.permissions import grant_object
|
||||
|
||||
|
||||
class _BaseNewStyleParser:
|
||||
@@ -1685,32 +1683,6 @@ class TestMetadataOverrides(TestCase):
|
||||
self.assertEqual(base.actor_id, 2)
|
||||
self.assertEqual(base.version_label, "v2")
|
||||
|
||||
def test_from_document_copies_user_and_group_grants_separately(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A document with view and change granted directly to one user and
|
||||
to a group with another user as a member
|
||||
WHEN:
|
||||
- Overrides are built from the document
|
||||
THEN:
|
||||
- The user lists hold only the directly granted user
|
||||
- The group lists hold the group
|
||||
"""
|
||||
direct_user = UserFactory()
|
||||
group_member = UserFactory()
|
||||
group = Group.objects.create(name="group")
|
||||
group_member.groups.add(group)
|
||||
document = DocumentFactory()
|
||||
grant_object(direct_user, document, "view_document", "change_document")
|
||||
grant_object(group, document, "view_document", "change_document")
|
||||
|
||||
overrides = DocumentMetadataOverrides.from_document(document)
|
||||
|
||||
self.assertEqual(overrides.view_users, [direct_user.pk])
|
||||
self.assertEqual(overrides.change_users, [direct_user.pk])
|
||||
self.assertEqual(overrides.view_groups, [group.pk])
|
||||
self.assertEqual(overrides.change_groups, [group.pk])
|
||||
|
||||
|
||||
class TestBarcodeApplyDetectedASN(TestCase):
|
||||
"""
|
||||
|
||||
@@ -4,16 +4,13 @@ from datetime import date
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.test import TestCase
|
||||
from django.test import override_settings
|
||||
from faker import Faker
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from documents.models import Correspondent
|
||||
from documents.models import Document
|
||||
from documents.tasks import empty_trash
|
||||
from paperless_testing.factories import DocumentFactory
|
||||
|
||||
|
||||
class TestDocument(TestCase):
|
||||
@@ -260,72 +257,3 @@ def test_suggestion_content() -> None:
|
||||
)
|
||||
doc.content = content_over_limit
|
||||
assert doc.suggestion_content == expected_cropped_content
|
||||
|
||||
|
||||
class TestPublicFilenameLength:
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_parser_registry(self, mocker: MockerFixture) -> None:
|
||||
# The parser registry reads ApplicationConfiguration from the database
|
||||
extensions = {"application/pdf": ".pdf", "image/jpeg": ".jpg"}
|
||||
mocker.patch(
|
||||
"documents.models.get_default_file_extension",
|
||||
side_effect=extensions.__getitem__,
|
||||
)
|
||||
|
||||
LIMIT = Document.MAX_PUBLIC_FILENAME_BYTES
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("title", "archive", "counter", "max_bytes", "tail"),
|
||||
[
|
||||
pytest.param("a" * 300, False, 0, LIMIT, ".jpg", id="ascii"),
|
||||
pytest.param("a" * 300, True, 0, LIMIT, ".pdf", id="archive"),
|
||||
pytest.param("ä" * 200, False, 0, LIMIT, ".jpg", id="umlaut"),
|
||||
pytest.param("ä" * 200, False, 1, LIMIT, "_01.jpg", id="count"),
|
||||
pytest.param("a" * 300, False, 1, 100, "_01.jpg", id="max"),
|
||||
],
|
||||
)
|
||||
def test_long_title_keeps_tail_within_limit(
|
||||
self,
|
||||
title: str,
|
||||
*,
|
||||
archive: bool,
|
||||
counter: int,
|
||||
max_bytes: int,
|
||||
tail: str,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A document whose title alone is longer than the byte limit
|
||||
WHEN:
|
||||
- The public file name is generated
|
||||
THEN:
|
||||
- The name ends with the counter and extension
|
||||
- The name fits in the limit
|
||||
"""
|
||||
name = DocumentFactory.build(
|
||||
title=title,
|
||||
mime_type="image/jpeg",
|
||||
).get_public_filename(
|
||||
archive=archive,
|
||||
counter=counter,
|
||||
max_bytes=max_bytes,
|
||||
)
|
||||
|
||||
assert name.endswith(tail)
|
||||
assert len(name.encode()) <= max_bytes
|
||||
|
||||
def test_long_title_counters_give_distinct_names(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A document whose title alone is longer than the byte limit
|
||||
WHEN:
|
||||
- Public file names are generated with increasing counters
|
||||
THEN:
|
||||
- Each counter produces a distinct name
|
||||
"""
|
||||
doc = DocumentFactory.build(title="ä" * 200, mime_type="application/pdf")
|
||||
|
||||
names = [doc.get_public_filename(counter=counter) for counter in range(3)]
|
||||
|
||||
assert len(set(names)) == 3
|
||||
assert all(len(n.encode()) <= self.LIMIT for n in names)
|
||||
@@ -50,7 +50,6 @@ from documents.settings import EXPORTER_FILE_NAME
|
||||
from documents.settings import EXPORTER_SHARE_LINK_BUNDLE_NAME
|
||||
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
||||
from documents.tests.utils import SampleDirMixin
|
||||
from paperless.models import ApplicationConfiguration
|
||||
from paperless_mail.models import MailAccount
|
||||
from paperless_testing.assertions import FileSystemAssertsMixin
|
||||
from paperless_testing.dirs import DirectoriesMixin
|
||||
@@ -1403,45 +1402,6 @@ class TestCryptExportImport(
|
||||
social_token = SocialToken.objects.first()
|
||||
self.assertIsNotNone(social_token)
|
||||
|
||||
def test_import_passphrase_with_float_field(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- An application configuration with a float field set
|
||||
WHEN:
|
||||
- Export and import are run with a passphrase
|
||||
THEN:
|
||||
- The import succeeds and the float value is preserved
|
||||
"""
|
||||
ApplicationConfiguration.objects.update_or_create(
|
||||
pk=1,
|
||||
defaults={"rotate_pages_threshold": 12.5},
|
||||
)
|
||||
|
||||
call_command(
|
||||
"document_exporter",
|
||||
"--no-progress-bar",
|
||||
"--passphrase",
|
||||
"securepassword",
|
||||
self.target,
|
||||
skip_checks=True,
|
||||
)
|
||||
|
||||
ApplicationConfiguration.objects.all().delete()
|
||||
|
||||
call_command(
|
||||
"document_importer",
|
||||
"--no-progress-bar",
|
||||
"--passphrase",
|
||||
"securepassword",
|
||||
self.target,
|
||||
skip_checks=True,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
ApplicationConfiguration.objects.get().rotate_pages_threshold,
|
||||
12.5,
|
||||
)
|
||||
|
||||
def test_import_crypt_no_passphrase(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
@@ -1577,70 +1537,3 @@ class TestExportDerivedFileNameCollisions:
|
||||
assert (
|
||||
target / record[EXPORTER_ARCHIVE_NAME]
|
||||
).read_text() == f"archive {pk}"
|
||||
|
||||
def test_long_titles_export_within_file_name_limit(
|
||||
self,
|
||||
paperless_dirs: PaperlessDirs,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Two documents with the same created date and the same title
|
||||
- The title alone is longer than 255 bytes and contains umlauts
|
||||
WHEN:
|
||||
- The exporter runs
|
||||
THEN:
|
||||
- The export finishes with distinct original, thumbnail and archive names
|
||||
- Every exported file name fits in the byte limit
|
||||
- Each exported file holds its own document's content
|
||||
"""
|
||||
for pk in (1, 2):
|
||||
(paperless_dirs.originals_dir / f"{pk:07}.pdf").write_bytes(
|
||||
f"original {pk}".encode(),
|
||||
)
|
||||
(paperless_dirs.archive_dir / f"{pk:07}.pdf").write_bytes(
|
||||
f"archive {pk}".encode(),
|
||||
)
|
||||
(paperless_dirs.thumbnail_dir / f"{pk:07}.webp").write_bytes(
|
||||
f"thumbnail {pk}".encode(),
|
||||
)
|
||||
DocumentFactory(
|
||||
pk=pk,
|
||||
title="Bestellbestätigung " * 15,
|
||||
created=date(2026, 10, 15),
|
||||
filename=f"{pk:07}.pdf",
|
||||
archive_filename=f"{pk:07}.pdf",
|
||||
mime_type="application/pdf",
|
||||
)
|
||||
|
||||
target = tmp_path / "export"
|
||||
target.mkdir()
|
||||
call_command(
|
||||
"document_exporter",
|
||||
str(target),
|
||||
"--no-progress-bar",
|
||||
skip_checks=True,
|
||||
)
|
||||
|
||||
manifest = json.loads((target / "manifest.json").read_text())
|
||||
records = {r["pk"]: r for r in manifest if r["model"] == "documents.document"}
|
||||
|
||||
for key in (
|
||||
EXPORTER_FILE_NAME,
|
||||
EXPORTER_THUMBNAIL_NAME,
|
||||
EXPORTER_ARCHIVE_NAME,
|
||||
):
|
||||
assert records[1][key] != records[2][key]
|
||||
|
||||
for pk in (1, 2):
|
||||
record = records[pk]
|
||||
for key, content in (
|
||||
(EXPORTER_FILE_NAME, f"original {pk}"),
|
||||
(EXPORTER_THUMBNAIL_NAME, f"thumbnail {pk}"),
|
||||
(EXPORTER_ARCHIVE_NAME, f"archive {pk}"),
|
||||
):
|
||||
assert (
|
||||
len(Path(record[key]).name.encode())
|
||||
<= Document.MAX_PUBLIC_FILENAME_BYTES
|
||||
)
|
||||
assert (target / record[key]).read_text() == content
|
||||
@@ -0,0 +1,520 @@
|
||||
"""
|
||||
Tests for documents.pdf_ops.
|
||||
|
||||
These use real PDFs from the sample directories. No database, Celery or mocks.
|
||||
Pages are compared by a hash of their content stream, so page identity and order
|
||||
are easy to assert.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pikepdf
|
||||
import pytest
|
||||
|
||||
from documents import pdf_ops
|
||||
from documents.pdf_ops import PageSpec
|
||||
|
||||
SRC_ROOT = Path(__file__).parents[2]
|
||||
SAMPLES = Path(__file__).parent / "samples"
|
||||
THREE_PAGES = SAMPLES / "documents" / "originals" / "0000002.pdf"
|
||||
TWELVE_PAGES = SAMPLES / "barcodes" / "split-by-asn-2.pdf"
|
||||
ENCRYPTED = SAMPLES / "password-is-test.pdf"
|
||||
SIGNED = SRC_ROOT / "paperless" / "tests" / "samples" / "tesseract" / "signed.pdf"
|
||||
|
||||
|
||||
def _page_fingerprint(page: pikepdf.Page) -> str:
|
||||
contents = page.obj.get("/Contents")
|
||||
assert contents is not None, "sample page has no /Contents"
|
||||
streams = list(contents) if isinstance(contents, pikepdf.Array) else [contents]
|
||||
return hashlib.sha256(b"".join(s.read_bytes() for s in streams)).hexdigest()
|
||||
|
||||
|
||||
def fingerprints(path: Path) -> list[str]:
|
||||
with pikepdf.open(path) as pdf:
|
||||
return [_page_fingerprint(page) for page in pdf.pages]
|
||||
|
||||
|
||||
def rotations(path: Path) -> list[int]:
|
||||
with pikepdf.open(path) as pdf:
|
||||
return [int(page.obj.get("/Rotate", 0)) for page in pdf.pages]
|
||||
|
||||
|
||||
def docinfo_keys(path: Path) -> set[str]:
|
||||
with pikepdf.open(path) as pdf:
|
||||
return set(pdf.docinfo.keys())
|
||||
|
||||
|
||||
def constant(path: Path) -> Callable[[], Path]:
|
||||
return lambda: path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def source_fingerprints() -> list[str]:
|
||||
fps = fingerprints(THREE_PAGES)
|
||||
assert len(set(fps)) == 3, "sample must have three distinct pages"
|
||||
return fps
|
||||
|
||||
|
||||
class TestRotatePdf:
|
||||
def test_rotation_is_relative_and_applies_to_every_page(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A three page PDF
|
||||
WHEN:
|
||||
- It is rotated by 90 degrees, then the result is rotated by 90 again
|
||||
THEN:
|
||||
- Every page is rotated relative to its current rotation
|
||||
- The page content itself is unchanged
|
||||
"""
|
||||
once = tmp_path / "once.pdf"
|
||||
twice = tmp_path / "twice.pdf"
|
||||
|
||||
pdf_ops.rotate_pdf(THREE_PAGES, once, 90)
|
||||
pdf_ops.rotate_pdf(once, twice, 90)
|
||||
|
||||
assert rotations(once) == [90, 90, 90]
|
||||
assert rotations(twice) == [180, 180, 180]
|
||||
assert fingerprints(twice) == fingerprints(THREE_PAGES)
|
||||
|
||||
def test_keeps_document_info(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A PDF with document info
|
||||
WHEN:
|
||||
- It is rotated
|
||||
THEN:
|
||||
- The document info is still present in the output
|
||||
"""
|
||||
dst = tmp_path / "out.pdf"
|
||||
|
||||
pdf_ops.rotate_pdf(THREE_PAGES, dst, 90)
|
||||
|
||||
assert "/Creator" in docinfo_keys(dst)
|
||||
|
||||
|
||||
class TestRemovePages:
|
||||
@pytest.mark.parametrize(
|
||||
("pages", "kept"),
|
||||
[
|
||||
pytest.param([2], [0, 2], id="single"),
|
||||
pytest.param([3, 1], [1], id="unordered"),
|
||||
pytest.param([2, 2], [0, 2], id="duplicates-remove-once"),
|
||||
pytest.param([], [0, 1, 2], id="empty-keeps-everything"),
|
||||
pytest.param([1, 2, 3], [], id="every-page"),
|
||||
],
|
||||
)
|
||||
def test_removes_only_the_requested_pages(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
source_fingerprints: list[str],
|
||||
pages: list[int],
|
||||
kept: list[int],
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A three page PDF
|
||||
WHEN:
|
||||
- Pages are removed, in any order and possibly repeated
|
||||
THEN:
|
||||
- Exactly the other pages remain, in their original order
|
||||
"""
|
||||
dst = tmp_path / "out.pdf"
|
||||
|
||||
pdf_ops.remove_pages(THREE_PAGES, dst, pages)
|
||||
|
||||
assert fingerprints(dst) == [source_fingerprints[i] for i in kept]
|
||||
|
||||
def test_keeps_document_info(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A PDF with document info
|
||||
WHEN:
|
||||
- A page is removed
|
||||
THEN:
|
||||
- The document info is still present in the output
|
||||
"""
|
||||
dst = tmp_path / "out.pdf"
|
||||
|
||||
pdf_ops.remove_pages(THREE_PAGES, dst, [1])
|
||||
|
||||
assert "/Creator" in docinfo_keys(dst)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bad_page",
|
||||
[
|
||||
pytest.param(0, id="zero"),
|
||||
pytest.param(-1, id="negative"),
|
||||
],
|
||||
)
|
||||
def test_rejects_pages_below_one(self, tmp_path: Path, bad_page: int) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A three page PDF
|
||||
WHEN:
|
||||
- Pages are removed and one of them is below 1
|
||||
THEN:
|
||||
- ValueError is raised
|
||||
- No output file is written
|
||||
"""
|
||||
dst = tmp_path / "out.pdf"
|
||||
|
||||
with pytest.raises(ValueError, match="start at 1"):
|
||||
pdf_ops.remove_pages(THREE_PAGES, dst, [1, bad_page])
|
||||
|
||||
assert not dst.exists()
|
||||
|
||||
|
||||
class TestBuildPdfs:
|
||||
def test_selects_and_orders_pages(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
source_fingerprints: list[str],
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A three page PDF
|
||||
WHEN:
|
||||
- One output is built from pages 3 then 1
|
||||
THEN:
|
||||
- The output has those pages in that order
|
||||
- Its path is returned
|
||||
"""
|
||||
dst = tmp_path / "out.pdf"
|
||||
|
||||
written = pdf_ops.build_pdfs(
|
||||
THREE_PAGES,
|
||||
[([PageSpec(3), PageSpec(1)], constant(dst))],
|
||||
)
|
||||
|
||||
assert written == [dst]
|
||||
assert fingerprints(dst) == [source_fingerprints[2], source_fingerprints[0]]
|
||||
|
||||
def test_rotates_only_the_requested_pages(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A three page PDF
|
||||
WHEN:
|
||||
- One output is built with a different rotation on each page
|
||||
THEN:
|
||||
- Each output page has exactly the rotation requested for it
|
||||
"""
|
||||
dst = tmp_path / "out.pdf"
|
||||
|
||||
pdf_ops.build_pdfs(
|
||||
THREE_PAGES,
|
||||
[([PageSpec(1), PageSpec(2, 90), PageSpec(3, 180)], constant(dst))],
|
||||
)
|
||||
|
||||
assert rotations(dst) == [0, 90, 180]
|
||||
|
||||
def test_writes_one_file_per_output_in_order(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A twelve page PDF
|
||||
WHEN:
|
||||
- Two outputs are built from different page ranges
|
||||
THEN:
|
||||
- Two files are written and returned in output order
|
||||
- Each holds exactly its own pages
|
||||
"""
|
||||
first = tmp_path / "first.pdf"
|
||||
second = tmp_path / "second.pdf"
|
||||
source = fingerprints(TWELVE_PAGES)
|
||||
|
||||
written = pdf_ops.build_pdfs(
|
||||
TWELVE_PAGES,
|
||||
[
|
||||
([PageSpec(p) for p in (1, 2, 3)], constant(first)),
|
||||
([PageSpec(p) for p in range(4, 13)], constant(second)),
|
||||
],
|
||||
)
|
||||
|
||||
assert written == [first, second]
|
||||
assert fingerprints(first) == source[:3]
|
||||
assert fingerprints(second) == source[3:]
|
||||
|
||||
def test_empty_page_list_writes_a_zero_page_file(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A three page PDF
|
||||
WHEN:
|
||||
- An output with no pages is built
|
||||
THEN:
|
||||
- A PDF with zero pages is written
|
||||
"""
|
||||
dst = tmp_path / "out.pdf"
|
||||
|
||||
pdf_ops.build_pdfs(THREE_PAGES, [([], constant(dst))])
|
||||
|
||||
assert fingerprints(dst) == []
|
||||
|
||||
def test_destination_is_not_requested_when_a_page_is_out_of_range(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A three page PDF
|
||||
WHEN:
|
||||
- Several outputs are built
|
||||
- A later output refers to a page past the end
|
||||
THEN:
|
||||
- IndexError is raised
|
||||
- No destination was requested for any output, including earlier valid ones
|
||||
"""
|
||||
requested: list[Path] = []
|
||||
|
||||
def make_dst() -> Path:
|
||||
requested.append(tmp_path / "out.pdf")
|
||||
return requested[-1]
|
||||
|
||||
with pytest.raises(IndexError):
|
||||
pdf_ops.build_pdfs(
|
||||
THREE_PAGES,
|
||||
[([PageSpec(1)], make_dst), ([PageSpec(99)], make_dst)],
|
||||
)
|
||||
|
||||
assert requested == []
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bad_page",
|
||||
[
|
||||
pytest.param(0, id="zero"),
|
||||
pytest.param(-1, id="negative"),
|
||||
],
|
||||
)
|
||||
def test_rejects_pages_below_one_before_opening_anything(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
bad_page: int,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A three page PDF
|
||||
WHEN:
|
||||
- Several outputs are built and a later one has a page below 1
|
||||
THEN:
|
||||
- ValueError is raised
|
||||
- No destination was requested for any output
|
||||
"""
|
||||
requested: list[Path] = []
|
||||
|
||||
def make_dst() -> Path:
|
||||
requested.append(tmp_path / "out.pdf")
|
||||
return requested[-1]
|
||||
|
||||
with pytest.raises(ValueError, match="start at 1"):
|
||||
pdf_ops.build_pdfs(
|
||||
THREE_PAGES,
|
||||
[([PageSpec(1)], make_dst), ([PageSpec(bad_page)], make_dst)],
|
||||
)
|
||||
|
||||
assert requested == []
|
||||
|
||||
|
||||
class TestValidatePageOperations:
|
||||
def test_returns_the_output_count(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Operations that all target the default output
|
||||
WHEN:
|
||||
- They are validated
|
||||
THEN:
|
||||
- One output document is reported
|
||||
"""
|
||||
operations = [{"page": 1}, {"page": 2}, {"page": 3}]
|
||||
|
||||
assert pdf_ops.validate_page_operations(operations, single_output=True) == 1
|
||||
|
||||
def test_gap_in_output_indices_counts_up_to_the_highest(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Operations that target outputs 0 and 2 but never 1
|
||||
WHEN:
|
||||
- They are validated
|
||||
THEN:
|
||||
- Three output documents are reported
|
||||
"""
|
||||
operations = [
|
||||
{"page": 1, "doc": 0},
|
||||
{"page": 2, "doc": 2},
|
||||
{"page": 3, "doc": 0},
|
||||
]
|
||||
|
||||
count = pdf_ops.validate_page_operations(operations, single_output=False)
|
||||
|
||||
assert count == 3
|
||||
|
||||
def test_empty_operations_are_rejected(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- No operations
|
||||
WHEN:
|
||||
- They are validated
|
||||
THEN:
|
||||
- ValueError is raised
|
||||
"""
|
||||
with pytest.raises(ValueError, match="index is out of bounds"):
|
||||
pdf_ops.validate_page_operations([], single_output=False)
|
||||
|
||||
def test_multiple_outputs_rejected_when_single_output_required(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Operations that target two outputs
|
||||
WHEN:
|
||||
- They are validated with a single output required
|
||||
THEN:
|
||||
- ValueError is raised
|
||||
"""
|
||||
operations = [{"page": 1, "doc": 0}, {"page": 2, "doc": 1}]
|
||||
|
||||
with pytest.raises(ValueError, match="Multiple output documents"):
|
||||
pdf_ops.validate_page_operations(operations, single_output=True)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"doc",
|
||||
[
|
||||
pytest.param(-1, id="negative"),
|
||||
pytest.param(2, id="equal-to-operation-count"),
|
||||
pytest.param(2**32, id="huge"),
|
||||
],
|
||||
)
|
||||
def test_output_index_out_of_bounds(self, doc: int) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Two operations, one with an output index that is out of bounds
|
||||
WHEN:
|
||||
- They are validated
|
||||
THEN:
|
||||
- ValueError is raised
|
||||
"""
|
||||
operations = [{"page": 1, "doc": 0}, {"page": 2, "doc": doc}]
|
||||
|
||||
with pytest.raises(ValueError, match="index is out of bounds"):
|
||||
pdf_ops.validate_page_operations(operations, single_output=False)
|
||||
|
||||
|
||||
class TestDecrypt:
|
||||
@pytest.mark.parametrize(
|
||||
("path", "expected"),
|
||||
[
|
||||
pytest.param(ENCRYPTED, True, id="password-required"),
|
||||
pytest.param(SIGNED, True, id="opens-without-password-but-encrypted"),
|
||||
pytest.param(THREE_PAGES, False, id="not-encrypted"),
|
||||
],
|
||||
)
|
||||
def test_needs_decrypt(self, path: Path, *, expected: bool) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A PDF that is encrypted, or encrypted but openable, or plain
|
||||
WHEN:
|
||||
- needs_decrypt is asked about it
|
||||
THEN:
|
||||
- Only the unencrypted PDF reports False
|
||||
"""
|
||||
assert pdf_ops.needs_decrypt(path) is expected
|
||||
|
||||
def test_decrypt_writes_an_unencrypted_copy(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A password protected PDF
|
||||
WHEN:
|
||||
- It is decrypted with the correct password
|
||||
THEN:
|
||||
- The path from make_dst is returned
|
||||
- The written copy no longer needs decrypting
|
||||
"""
|
||||
dst = tmp_path / "out.pdf"
|
||||
|
||||
result = pdf_ops.decrypt_pdf(ENCRYPTED, constant(dst), "test")
|
||||
|
||||
assert result == dst
|
||||
assert pdf_ops.needs_decrypt(dst) is False
|
||||
|
||||
def test_wrong_password_raises_and_never_requests_a_destination(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A password protected PDF
|
||||
WHEN:
|
||||
- It is decrypted with the wrong password
|
||||
THEN:
|
||||
- PasswordError is raised
|
||||
- No destination was requested
|
||||
"""
|
||||
requested: list[Path] = []
|
||||
|
||||
def make_dst() -> Path:
|
||||
requested.append(tmp_path / "out.pdf")
|
||||
return requested[-1]
|
||||
|
||||
with pytest.raises(pikepdf.PasswordError):
|
||||
pdf_ops.decrypt_pdf(ENCRYPTED, make_dst, "wrong")
|
||||
|
||||
assert requested == []
|
||||
|
||||
|
||||
class TestPdfMerger:
|
||||
def test_pages_are_appended_in_the_order_added(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
source_fingerprints: list[str],
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A reordered PDF and the original three page PDF
|
||||
WHEN:
|
||||
- Both are added to a merger in that order and saved
|
||||
THEN:
|
||||
- The output holds all pages in the order they were added
|
||||
"""
|
||||
reordered = tmp_path / "reordered.pdf"
|
||||
merged = tmp_path / "merged.pdf"
|
||||
pdf_ops.build_pdfs(
|
||||
THREE_PAGES,
|
||||
[([PageSpec(3), PageSpec(1)], constant(reordered))],
|
||||
)
|
||||
|
||||
with pdf_ops.PdfMerger() as merger:
|
||||
merger.add(reordered)
|
||||
merger.add(THREE_PAGES)
|
||||
merger.save(merged)
|
||||
|
||||
assert fingerprints(merged) == [
|
||||
source_fingerprints[2],
|
||||
source_fingerprints[0],
|
||||
*source_fingerprints,
|
||||
]
|
||||
|
||||
def test_output_version_is_at_least_the_highest_source_version(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Two PDFs with different PDF versions
|
||||
WHEN:
|
||||
- Both are added to a merger and saved
|
||||
THEN:
|
||||
- The output version is at least the highest source version
|
||||
"""
|
||||
merged = tmp_path / "merged.pdf"
|
||||
with pikepdf.open(TWELVE_PAGES) as pdf:
|
||||
source_versions = [pdf.pdf_version]
|
||||
with pikepdf.open(THREE_PAGES) as pdf:
|
||||
source_versions.append(pdf.pdf_version)
|
||||
|
||||
with pdf_ops.PdfMerger() as merger:
|
||||
merger.add(TWELVE_PAGES)
|
||||
merger.add(THREE_PAGES)
|
||||
merger.save(merged)
|
||||
|
||||
with pikepdf.open(merged) as pdf:
|
||||
assert pdf.pdf_version >= max(source_versions)
|
||||
@@ -7,7 +7,6 @@ from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import override_settings
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
from rest_framework import status
|
||||
@@ -226,25 +225,6 @@ class ShareLinkBundleAPITests(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
|
||||
self.assertIn("sharelink_notfound=1", response["Location"])
|
||||
|
||||
@override_settings(LOGIN_URL="/paperless/accounts/login/")
|
||||
def test_unknown_share_link_redirects_under_subpath(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Paperless is served under a subpath
|
||||
WHEN:
|
||||
- An unknown public share link is requested
|
||||
THEN:
|
||||
- The redirect to the login page keeps the subpath
|
||||
"""
|
||||
self.client.logout()
|
||||
response = self.client.get("/share/unknownsharelink/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
|
||||
self.assertEqual(
|
||||
response["Location"],
|
||||
"/paperless/accounts/login/?sharelink_notfound=1",
|
||||
)
|
||||
|
||||
def test_share_link_missing_file_redirects(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
@@ -6176,50 +6176,6 @@ class TestApplyAISuggestionsWorkflowAction(
|
||||
|
||||
self.assertEqual(self.doc.tags.count(), 0)
|
||||
|
||||
def test_unowned_document_does_not_get_private_objects_by_id(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- An unowned document
|
||||
- AI suggestions selecting another user's private tag and
|
||||
correspondent by ID, plus an unowned tag
|
||||
WHEN:
|
||||
- The suggestions are applied
|
||||
THEN:
|
||||
- Only the unowned tag is assigned; an unowned document must not
|
||||
be treated as unrestricted
|
||||
"""
|
||||
self.doc.owner = None
|
||||
self.doc.save(update_fields=["owner"])
|
||||
other = User.objects.create(username="someone-else")
|
||||
private_tag = Tag.objects.create(name="Private Tag", owner=other)
|
||||
public_tag = Tag.objects.create(name="Public Tag", owner=None)
|
||||
private_correspondent = Correspondent.objects.create(
|
||||
name="Private Correspondent",
|
||||
owner=other,
|
||||
)
|
||||
action = self.make_action(
|
||||
ai_suggestion_fields=[
|
||||
WorkflowAction.AISuggestionField.TAGS,
|
||||
WorkflowAction.AISuggestionField.CORRESPONDENT,
|
||||
],
|
||||
)
|
||||
suggestions: ClassificationSuggestions = {
|
||||
**SUGGESTIONS,
|
||||
"tags": {
|
||||
"existing_ids": [private_tag.pk, public_tag.pk],
|
||||
"new_names": [],
|
||||
},
|
||||
"correspondents": {
|
||||
"existing_ids": [private_correspondent.pk],
|
||||
"new_names": [],
|
||||
},
|
||||
}
|
||||
|
||||
self.apply(action, suggestions)
|
||||
|
||||
self.assertEqual(list(self.doc.tags.all()), [public_tag])
|
||||
self.assertIsNone(self.doc.correspondent)
|
||||
|
||||
def test_unparsable_dates_are_skipped(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
+26
-40
@@ -289,7 +289,6 @@ logger = logging.getLogger("paperless.api")
|
||||
# clauses efficiently, so this threshold mainly protects SQLite users.
|
||||
_TANTIVY_INTERSECT_THRESHOLD = 5_000
|
||||
_TANTIVY_SEARCH_PARAM_NAMES = ("text", "title_search", "query", "more_like_id")
|
||||
_TANTIVY_COMBINABLE_SEARCH_PARAMS = ({"text", "query"}, {"title_search", "query"})
|
||||
|
||||
# whoosh-compat's fieldname tagger (used only for SearchMode.QUERY, via the
|
||||
# whoosh grammar in parse_user_query) is O(n^2) in plain word characters:
|
||||
@@ -309,18 +308,6 @@ _TANTIVY_COMBINABLE_SEARCH_PARAMS = ({"text", "query"}, {"title_search", "query"
|
||||
_MAX_QUERY_LENGTH: Final[int] = 4096
|
||||
|
||||
|
||||
def _validate_tantivy_search_params(active: list[str]) -> None:
|
||||
# may also have a filter query alongside a simple search e.g. relative dates
|
||||
if len(active) > 1 and set(active) not in _TANTIVY_COMBINABLE_SEARCH_PARAMS:
|
||||
raise ValidationError(
|
||||
{
|
||||
"detail": _(
|
||||
"Specify only one of text, title_search, query, or more_like_id.",
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _get_tantivy_query_and_mode(params):
|
||||
from documents.search import QueryTooLongError
|
||||
from documents.search import SearchMode
|
||||
@@ -334,15 +321,9 @@ def _get_tantivy_query_and_mode(params):
|
||||
else:
|
||||
return None # pragma: no cover
|
||||
|
||||
filter_query = (
|
||||
str(params["query"])
|
||||
if mode is not SearchMode.QUERY and "query" in params
|
||||
else None
|
||||
)
|
||||
for value in (raw, filter_query):
|
||||
if value is not None and len(value) > _MAX_QUERY_LENGTH:
|
||||
raise QueryTooLongError(len(value), _MAX_QUERY_LENGTH)
|
||||
return raw, mode, filter_query
|
||||
if len(raw) > _MAX_QUERY_LENGTH:
|
||||
raise QueryTooLongError(len(raw), _MAX_QUERY_LENGTH)
|
||||
return raw, mode
|
||||
|
||||
|
||||
def _get_more_like_id(query_params: dict[str, Any], user: User | None) -> int:
|
||||
@@ -2585,9 +2566,15 @@ class UnifiedSearchViewSet(DocumentViewSet):
|
||||
|
||||
def parse_search_params() -> SearchParams:
|
||||
"""Extract query string, search mode, and ordering from request."""
|
||||
_validate_tantivy_search_params(
|
||||
self._get_active_search_params(request),
|
||||
)
|
||||
active = self._get_active_search_params(request)
|
||||
if len(active) > 1:
|
||||
raise ValidationError(
|
||||
{
|
||||
"detail": _(
|
||||
"Specify only one of text, title_search, query, or more_like_id.",
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
ordering_param = request.query_params.get("ordering", "")
|
||||
sort_reverse = ordering_param.startswith("-")
|
||||
@@ -2649,9 +2636,7 @@ class UnifiedSearchViewSet(DocumentViewSet):
|
||||
filtered_qs: QuerySet[Document],
|
||||
) -> SearchResultPage:
|
||||
"""Handle text/title/query search: IDs, ORM intersection, page highlights."""
|
||||
query_str, search_mode, filter_query = _get_tantivy_query_and_mode(
|
||||
request.query_params,
|
||||
)
|
||||
query_str, search_mode = _get_tantivy_query_and_mode(request.query_params)
|
||||
|
||||
# "score" is not a real Tantivy sort field — it means relevance order,
|
||||
# which is Tantivy's default when no sort field is specified.
|
||||
@@ -2664,7 +2649,6 @@ class UnifiedSearchViewSet(DocumentViewSet):
|
||||
),
|
||||
sort_reverse=sort_reverse,
|
||||
search_mode=search_mode,
|
||||
filter_query=filter_query,
|
||||
)
|
||||
ordered_ids = intersect_and_order(
|
||||
all_ids,
|
||||
@@ -2899,7 +2883,14 @@ class DocumentSelectionMixin:
|
||||
]
|
||||
if not search_filters:
|
||||
return None
|
||||
_validate_tantivy_search_params(search_filters)
|
||||
if len(search_filters) > 1:
|
||||
raise ValidationError(
|
||||
{
|
||||
"detail": _(
|
||||
"Specify only one of text, title_search, query, or more_like_id.",
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
from documents.search import SearchQueryError
|
||||
from documents.search import get_backend
|
||||
@@ -2918,14 +2909,11 @@ class DocumentSelectionMixin:
|
||||
user=search_user,
|
||||
)
|
||||
else:
|
||||
query_str, search_mode, filter_query = _get_tantivy_query_and_mode(
|
||||
filters,
|
||||
)
|
||||
query_str, search_mode = _get_tantivy_query_and_mode(filters)
|
||||
search_ids = backend.search_ids(
|
||||
query_str,
|
||||
user=search_user,
|
||||
search_mode=search_mode,
|
||||
filter_query=filter_query,
|
||||
)
|
||||
except SearchQueryError as e:
|
||||
# Same user-fixable-query mapping as the search list endpoint:
|
||||
@@ -4911,7 +4899,7 @@ class SharedLinkView(View):
|
||||
share_link.expiration is not None
|
||||
and share_link.expiration < timezone.now()
|
||||
):
|
||||
return HttpResponseRedirect(f"{settings.LOGIN_URL}?sharelink_expired=1")
|
||||
return HttpResponseRedirect("/accounts/login/?sharelink_expired=1")
|
||||
try:
|
||||
return serve_file(
|
||||
doc=share_link.document,
|
||||
@@ -4920,16 +4908,14 @@ class SharedLinkView(View):
|
||||
disposition="inline",
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return HttpResponseRedirect(
|
||||
f"{settings.LOGIN_URL}?sharelink_notfound=1",
|
||||
)
|
||||
return HttpResponseRedirect("/accounts/login/?sharelink_notfound=1")
|
||||
|
||||
bundle = ShareLinkBundle.objects.filter(slug=slug).first()
|
||||
if bundle is None:
|
||||
return HttpResponseRedirect(f"{settings.LOGIN_URL}?sharelink_notfound=1")
|
||||
return HttpResponseRedirect("/accounts/login/?sharelink_notfound=1")
|
||||
|
||||
if bundle.expiration is not None and bundle.expiration < timezone.now():
|
||||
return HttpResponseRedirect(f"{settings.LOGIN_URL}?sharelink_expired=1")
|
||||
return HttpResponseRedirect("/accounts/login/?sharelink_expired=1")
|
||||
|
||||
if bundle.status in {
|
||||
ShareLinkBundle.Status.PENDING,
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Afrikaans\n"
|
||||
"Language: af_ZA\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Ongeldige veranderlike bespeur."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Amharic\n"
|
||||
"Language: am_ET\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Arabic\n"
|
||||
"Language: ar_SA\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "اكتشاف متغير خاطئ."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "كخطوة أخيرة، يرجى ملء النموذج التالي:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Belarusian\n"
|
||||
"Language: be_BY\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Выяўлена няправільная зменная."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Bulgarian\n"
|
||||
"Language: bg_BG\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "стартиране на работните процеси"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Засечена е невалидна променлива."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Като последна стъпка, моля, попълнете с
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Catalan\n"
|
||||
"Language: ca_ES\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "flux corrents"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Permisos insuficients."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Variable detectada invàlida."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Duplicat d'identificadors de documents no permès."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Documents no trobats: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Com a pas final, completa el següent formulari:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Especifica només un dels següents valors: text, title_search, query o more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Invalid more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Configuració AI invàlida."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Especifica només un dels següents valors: text, title_search, query o more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Permisos insuficients per compartir document %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paquet ja s'està processant."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "El paquet de link encarà s'està preparant. Prova de nou més tard."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "El paquet d'enllaç no està disponible."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Czech\n"
|
||||
"Language: cs_CZ\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "spuštění pracovních postupů"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Nedostatečná oprávnění."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Zjištěna neplatná proměnná."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Jako poslední krok vyplňte následující formulář:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Nedostatečná oprávnění ke sdílení dokumentu %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Danish\n"
|
||||
"Language: da_DK\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "workflow-kørsler"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Ugyldig variabel fundet."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Som sidste trin, udfyld venligst flg. formular:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German, Switzerland\n"
|
||||
"Language: de_CH\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "Arbeitsablauf wird ausgeführt"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Unzureichende Berechtigungen."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Ungültige Variable erkannt."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Doppelte Dokumentbezeichner sind nicht erlaubt."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumente nicht gefunden: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Bitte füllen Sie zum Abschluss folgendes Formular aus:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Geben Sie nur einen von text, title_search, query, oder more_like_id an."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Ungültige more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Ungültige KI-Konfiguration."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Geben Sie nur einen von text, title_search, query, oder more_like_id an."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Unzureichende Berechtigungen, um Dokument %(id)s zu teilen."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paket wird bereits verarbeitet."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Das Freigabelink-Paket wird noch vorbereitet. Bitte versuchen Sie es später erneut."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Das Freigabelink-Paket ist nicht verfügbar."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "Arbeitsablauf wird ausgeführt"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Unzureichende Berechtigungen."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Ungültige Variable erkannt."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Doppelte Dokumentbezeichner sind nicht erlaubt."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumente nicht gefunden: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Bitte füllen Sie zum Abschluss folgendes Formular aus:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Geben Sie nur einen von text, title_search, query, oder more_like_id an."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Ungültige more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Ungültige KI-Konfiguration."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Zeitüberschreitung bei der KI-Backendanfrage."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "Das KI-Backend hat die Anfrage abgelehnt. Details in den Protokollen."
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Geben Sie nur einen von text, title_search, query, oder more_like_id an."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Unzureichende Berechtigungen, um Dokument %(id)s zu teilen."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paket wird bereits verarbeitet."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Das Freigabelink-Paket wird noch vorbereitet. Bitte versuchen Sie es später erneut."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Das Freigabelink-Paket ist nicht verfügbar."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Greek\n"
|
||||
"Language: el_GR\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "εκτελέσεις ροής εργασίας"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Εντοπίστηκε μη έγκυρη μεταβλητή."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Ως τελικό βήμα, παρακαλούμε να συμπληρώ
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "ejecuciones de flujo de trabajo"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Permisos insuficientes."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Variable inválida."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "No se permiten identificadores de documento duplicados."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Documentos no encontrados: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Como último paso, por favor complete el siguiente formulario:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Especifique solo uno entre text, title_search, query, o more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Configuración de IA inválida."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Especifique solo uno entre text, title_search, query, o more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Permisos insuficientes para compartir el documento %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "El paquete ya está siendo procesado."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "El paquete de enlace compartido aún está siendo preparado. Por favor, inténtalo de nuevo más tarde."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "El paquete de enlace compartido no está disponible."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Estonian\n"
|
||||
"Language: et_EE\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Persian\n"
|
||||
"Language: fa_IR\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "گردش کار اجرا می شود"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "متغیر نامعتبر شناسایی شده است."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr "به عنوان مرحله آخر ، لطفاً فرم زیر را تک
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Finnish\n"
|
||||
"Language: fi_FI\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Virheellinen muuttuja havaittu."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "le flux de travail s'exécute"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Droits insuffisants."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Variable invalide détectée."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Les identificateurs de document en double ne sont pas autorisés."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Documents introuvables : %(ids)s"
|
||||
@@ -1916,40 +1916,40 @@ msgstr "Pour finir, veuillez remplir le formulaire suivant :"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Spécifiez seulement un texte, titre, recherche ou more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "More_like_id invalide"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Configuration IA invalide."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "La requête d'arrière-plan IA a expiré."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Spécifiez seulement un texte, titre, recherche ou more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Droits d'accès insuffisant pour partager %(id)s document."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Le paquet est déjà en cours de traitement."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Le lot de liens de partage est en cours de préparation. Veuillez réessayer plus tard."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Le lot de liens de partage n'est pas disponible."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Hebrew\n"
|
||||
"Language: he_IL\n"
|
||||
@@ -1637,7 +1637,7 @@ msgid "workflow runs"
|
||||
msgstr "הרצות זרימת עבודה"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "הרשאות אינן מספיקות."
|
||||
@@ -1677,7 +1677,7 @@ msgstr "משתנה לא חוקי זוהה."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "מזהי מסמכים כפולים אינם מורשים."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "מסמכים לא נמצאו: %(ids)s"
|
||||
@@ -1918,40 +1918,40 @@ msgstr ": כצעד אחרון, אנא מלא את הטופס הבא:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "יש לציין רק אחד מהבאים: text, title_search, query או more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "מזהה more_like_id אינו תקין"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "הגדרות בינה מלאכותית שגויות."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "יש לציין רק אחד מהבאים: text, title_search, query או more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "הרשאות לא מספיקות לשיתוף מסמך %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "החבילה (Bundle) כבר נמצאת בתהליך עיבוד."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "חבילת קישור השיתוף עדיין בהכנה. נא לנסות שוב מאוחר יותר."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "חבילת קישור השיתוף אינה זמינה."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Hindi\n"
|
||||
"Language: hi_IN\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Croatian\n"
|
||||
"Language: hr_HR\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "tijek rada pokrenut"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Nedovoljne ovlasti."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Otkrivena je nevaljana vrsta datoteke."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Duplicirani identifikatori dokumenata nisu dopušteni."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumenti nisu pronađeni: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Kao posljednji korak ispunite sljedeći obrazac:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Navedite samo jedno od: text, title_search, query ili more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Nevažeći more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Nevažeća AI konfiguracija."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Zahtjev prema AI pozadinskom sustavu je istekao."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "AI pozadinski sustav je odbio zahtjev. Provjerite zapise (logs) za više detalja."
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Navedite samo jedno od: text, title_search, query ili more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Nedovoljne ovlasti za dijeljenje dokumenta %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paket se već obrađuje."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Paket linka za dijeljenje se još priprema. Pokušajte ponovo kasnije."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Paket linka za dijeljenje nije dostupan."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Hungarian\n"
|
||||
"Language: hu_HU\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "munkafolyamat futtatások"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Nincs jogosúltsága."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Érvénytelen változó észlelve."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "A dokumentumazonosítók duplikálása nem megengedett."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumentumok nem találhatók: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Utolsó lépésként kérjük válaszoljon az alábbi kérdésekre:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "A text, title_search, query, vagy more_like_id közül csak egyet adjon meg."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Érvénytelen more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Érvénytelen MI konfiguráció."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "A text, title_search, query, vagy more_like_id közül csak egyet adjon meg."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Nincs megfelelő jogosultság a %(id)s dokumentum megosztásához."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "A csomag feldolgozása már folyamatban van."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "A megosztási linkcsomag készítése folyamatban. Kérjük, próbálja meg később."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "A megosztási linkcsomag nem elérhető."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Indonesian\n"
|
||||
"Language: id_ID\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "daftar jalankan alur kerja"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Izin tidak mencukupi"
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Variabel ilegal terdeteksi."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Penggunaan pengenal dokumen ganda tidak diperbolehkan."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumen tidak ditemukan: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Sebagai langkah terakhir, mohon formulir berikut:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Izin tidak mencukupi untuk berbagi dokumen %(id)s"
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paket sedang diproses."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Bundel tautan berbagi masih dalam proses persiapan. Silakan coba lagi nanti."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Bundel tautan berbagi tidak tersedia."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it_IT\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "esecuzioni del flusso di lavoro"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Autorizzazioni insufficienti."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Variabile non valida rilevata."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Non sono consentiti identificatori di documenti duplicati."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Documenti non trovati: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Come ultimo passo, compila il seguente modulo:"
|
||||
msgid ", "
|
||||
msgstr ", "
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Specificare solo uno tra text, title_search, query o more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "more_like_id non valido"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Configurazione AI non valida."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Richiesta di backend AI scaduta."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "Il backend IA ha rifiutato la richiesta. Controlla i log per i dettagli."
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Specificare solo uno tra text, title_search, query o more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Autorizzazioni insufficienti per condividere il documento %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Il pacchetto è già in fase di elaborazione."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Il pacchetto di link di condivisione è ancora in fase di preparazione. Riprova più tardi."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Il pacchetto di link di condivisione non è disponibile."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-15 12:29\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Language: ja_JP\n"
|
||||
@@ -136,7 +136,7 @@ msgstr "タグ"
|
||||
|
||||
#: documents/models.py:125
|
||||
msgid "Cannot set itself as parent."
|
||||
msgstr "親として設定することはできません。"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:127
|
||||
msgid "Cannot set parent to a descendant."
|
||||
@@ -249,23 +249,23 @@ msgstr "物理的なドキュメントアーカイブ内での、このドキュ
|
||||
|
||||
#: documents/models.py:321
|
||||
msgid "root document for this version"
|
||||
msgstr "このバージョンのルートドキュメント"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "version index"
|
||||
msgstr "バージョンのインデックス"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:329
|
||||
msgid "Index of this version within the root document."
|
||||
msgstr "ルートドキュメント内における、このバージョンのインデックス"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "version label"
|
||||
msgstr "バージョンラベル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:337
|
||||
msgid "Optional short label for a document version."
|
||||
msgstr "ドキュメントバージョンの任意の短いラベル。"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:342 documents/models.py:961 documents/models.py:992
|
||||
#: documents/models.py:1096 documents/models.py:2126
|
||||
@@ -282,119 +282,119 @@ msgstr "アーカイブ"
|
||||
|
||||
#: documents/models.py:563
|
||||
msgid "Bank"
|
||||
msgstr "銀行"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:564
|
||||
msgid "Basket"
|
||||
msgstr "買い物かご"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:565
|
||||
msgid "Bell"
|
||||
msgstr "ベル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:566
|
||||
msgid "Bookmark"
|
||||
msgstr "ブックマーク"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:567
|
||||
msgid "Boxes"
|
||||
msgstr "箱"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:568
|
||||
msgid "Briefcase"
|
||||
msgstr "ブリーフケース"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:569
|
||||
msgid "Building"
|
||||
msgstr "建物"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:570
|
||||
msgid "Calculator"
|
||||
msgstr "電卓"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:571
|
||||
msgid "Calendar"
|
||||
msgstr "カレンダー"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:572
|
||||
msgid "Camera"
|
||||
msgstr "カメラ"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:573
|
||||
msgid "Checklist"
|
||||
msgstr "チェックリスト"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:574
|
||||
msgid "Cash"
|
||||
msgstr "現金"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:575
|
||||
msgid "Chat"
|
||||
msgstr "チャット"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:576
|
||||
msgid "Check"
|
||||
msgstr "確認"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:577
|
||||
msgid "Clipboard"
|
||||
msgstr "クリップボード"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:578
|
||||
msgid "Clock"
|
||||
msgstr "時計"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:579
|
||||
msgid "Credit card"
|
||||
msgstr "クレジットカード"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:580
|
||||
msgid "Download"
|
||||
msgstr "ダウンロード"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:581
|
||||
msgid "Envelope"
|
||||
msgstr "封筒"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:582
|
||||
msgid "Warning"
|
||||
msgstr "警告"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:583
|
||||
msgid "File"
|
||||
msgstr "ファイル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:584
|
||||
msgid "Checked file"
|
||||
msgstr "チェック済みファイル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:585
|
||||
msgid "Locked file"
|
||||
msgstr "ロックされたファイル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:586
|
||||
msgid "Medical file"
|
||||
msgstr "医療ファイル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:587
|
||||
msgid "Person file"
|
||||
msgstr "個人ファイル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:590
|
||||
msgid "Spreadsheet"
|
||||
msgstr "スプレッドシート"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:592
|
||||
msgid "Text file"
|
||||
msgstr "テキストファイル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:593
|
||||
msgid "Files"
|
||||
msgstr "ファイル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:594
|
||||
msgid "Folder"
|
||||
msgstr "フォルダー"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:595 paperless_mail/admin.py:58
|
||||
msgid "Filter"
|
||||
@@ -406,11 +406,11 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:597
|
||||
msgid "Globe"
|
||||
msgstr "地球儀"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:598
|
||||
msgid "Hash"
|
||||
msgstr "ハッシュ"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:599
|
||||
msgid "Heart"
|
||||
@@ -418,27 +418,27 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:600
|
||||
msgid "House"
|
||||
msgstr "家"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:601
|
||||
msgid "Inbox"
|
||||
msgstr "受信トレイ"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:602
|
||||
msgid "Journals"
|
||||
msgstr "日記"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:603
|
||||
msgid "Task list"
|
||||
msgstr "タスクリスト"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:604
|
||||
msgid "Newspaper"
|
||||
msgstr "新聞"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:605
|
||||
msgid "Attachment"
|
||||
msgstr "添付ファイル"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:606
|
||||
msgid "People"
|
||||
@@ -446,15 +446,15 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:607
|
||||
msgid "Person"
|
||||
msgstr "人物"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:608
|
||||
msgid "Printer"
|
||||
msgstr "プリンター"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:609
|
||||
msgid "Receipt"
|
||||
msgstr "領収書"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:610
|
||||
msgid "Safe"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "ワークフローの実行"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "無効な変数を検出しました"
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "最後のステップとして、以下のフォームに記入してく
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Korean\n"
|
||||
"Language: ko_KR\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "워크플로우 실행"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "잘못된 변수가 감지되었습니다."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "마지막 단계로 다음 양식을 작성해 주세요:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Luxembourgish\n"
|
||||
"Language: lb_LU\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Ongëlteg Zeechen detektéiert."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Lithuanian\n"
|
||||
"Language: lt_LT\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr "Galiausiai užpildykite šią formą:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Nepakanka leidimų bendrinti dokumentą %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Rinkinys jau apdorojamas."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Bendrinimo nuorodų rinkinys vis dar ruošiamas. Bandykite dar kartą vėliau."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Dalinimos nuorodų rinkinys yra nepasiekiamas."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Latvian\n"
|
||||
"Language: lv_LV\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Macedonian\n"
|
||||
"Language: mk_MK\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Malay\n"
|
||||
"Language: ms_MY\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Ongeldige variabele ontdekt."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Vul als laatste stap het volgende formulier in:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Norwegian\n"
|
||||
"Language: no_NO\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "workflow runs (NO)"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Utilstrekkelige tillatelser."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Ugyldig variabel oppdaget."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Duplicate document identifiers are not allowed. (NO)"
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Documents not found: %(ids)s (NO)"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "As a final step, please complete the following form: (NO)"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Specify only one of text, title_search, query, or more_like_id. (NO)"
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Invalid more_like_id (NO)"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Invalid AI configuration. (NO)"
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Forespørselen til KI-tjenesten fikk tidsavbrudd."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Specify only one of text, title_search, query, or more_like_id. (NO)"
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Insufficient permissions to share document %(id)s. (NO)"
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Bundle is already being processed. (NO)"
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "The share link bundle is still being prepared. Please try again later. (NO)"
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "The share link bundle is unavailable. (NO)"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"Language: pl_PL\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "uruchomienia przepływu pracy"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Niewystarczające uprawnienia."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Wykryto nieprawidłową zmienną."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Nie dopuszcza się powielania identyfikatorów dokumentów."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Nie znaleziono dokumentów: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Na koniec należy wypełnić poniższy formularz:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Nieprawidłowy more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Brak uprawnień do udostępnienia dokumentu %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Pakiet jest już przetwarzany."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Pakiet do udostępnienia jest nadal przygotowywany. Spróbuj ponownie później."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Pakiet do udostępnienia jest niedostępny."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese, Brazilian\n"
|
||||
"Language: pt_BR\n"
|
||||
@@ -1637,7 +1637,7 @@ msgid "workflow runs"
|
||||
msgstr "execução de fluxo de trabalho"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Permissões insuficientes."
|
||||
@@ -1677,7 +1677,7 @@ msgstr "Variável inválida detectada."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Não são permitidos identificadores de documento duplicados."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Documentos não encontrados: %(ids)s"
|
||||
@@ -1918,40 +1918,40 @@ msgstr "Como etapa final, por favor preencha o seguinte formulário:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Especifique apenas um text, title_search, consulta ou mais_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "more_like_id inválido"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Configuração de IA inválida."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "A requisição backend da IA expirou."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Especifique apenas um text, title_search, consulta ou mais_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Permissões insuficientes para compartilhar o documento %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Pacote já está sendo processado."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "O pacote de link de compartilhamento ainda está sendo preparado. Tente novamente mais tarde."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "O pacote de link de compartilhamento não está disponível."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Language: pt_PT\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Variável inválida detetada."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Romanian\n"
|
||||
"Language: ro_RO\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "rulări flux de lucru"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Variabilă nevalidă detectată."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "În etapa finală, vă rugăm să completați următorul formular:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru_RU\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "запуски рабочего процесса"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Недостаточно прав."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Обнаружена неверная переменная."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Повторяющиеся идентификаторы документа не допускаются."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Документы не найдены: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "На завершение, пожалуйста заполните сл
|
||||
msgid ", "
|
||||
msgstr ", "
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Укажите только одно: текст, title_search, query или more _like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Недопустимый more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Неверная конфигурация ИИ."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Таймаут запроса ИИ."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "Движок ИИ отклонил запрос. Проверьте логи."
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Укажите только одно: текст, title_search, query или more _like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Недостаточно прав для совместного доступа к документу %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Пакет уже обрабатывается."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Набор ссылок все еще готовится. Пожалуйста, повторите попытку позже."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Пакет общих ссылок недоступен."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:40\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Slovak\n"
|
||||
"Language: sk_SK\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "spustenia pracovných postupov"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Nedostatočné oprávnenia."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Zistená neplatná premenná."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Duplicitné identifikátory dokumentov nie sú povolené."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumenty sa nenašli: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Ako posledný krok vyplňte nasledujúci formulár:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Zadajte iba jednu z možností: text, title_search, query alebo more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Neplatné more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Neplatná konfigurácia umelej inteligencie."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Vypršal časový limit požiadavky na backend umelej inteligencie."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "Backend umelej inteligencie zamietol požiadavku. Podrobnosti nájdete v záznamoch."
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Zadajte iba jednu z možností: text, title_search, query alebo more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Nedostatočné oprávnenia na zdieľanie dokumentu %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Balík sa už spracúva."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Balík odkazov na zdieľanie sa stále pripravuje. Skúste to znova neskôr."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Balík odkazov na zdieľanie nie je k dispozícii."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Slovenian\n"
|
||||
"Language: sl_SI\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "poteka dela"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Nezadostna dovoljenja."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Zaznani neveljavni znaki."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Podvojeni identifikatorji dokumentov niso dovoljeni."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumentov ni bilo mogoče najti: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Kot zadnji korak vas prosimo, da izpolnite naslednji obrazec:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Navedite samo eno od naslednjih vrednosti: text, title_search, query ali more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Neveljaven more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Neveljavna konfiguracija umetne inteligence."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Časovna omejitev zahteve za umetno inteligenco je potekla."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "Zaledni sistem umetne inteligence je zavrnil zahtevo. Za podrobnosti preverite dnevnike."
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Navedite samo eno od naslednjih vrednosti: text, title_search, query ali more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Nezadostna dovoljenja za skupno rabo dokumenta %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paket se že obdeluje."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Paket povezav za deljenje je še vedno v pripravi. Poskusite znova pozneje."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Paket povezav za deljenje ni na voljo."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Albanian\n"
|
||||
"Language: sq_AL\n"
|
||||
@@ -482,7 +482,7 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:616
|
||||
msgid "Tag"
|
||||
msgstr "Etiketa"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:617 documents/models.py:632 documents/models.py:1804
|
||||
msgid "Tags"
|
||||
@@ -494,15 +494,15 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:619
|
||||
msgid "Truck"
|
||||
msgstr "Kamion"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:620
|
||||
msgid "Barcode"
|
||||
msgstr "Barkodi"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:621
|
||||
msgid "Wallet"
|
||||
msgstr "Portofol"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:624
|
||||
msgid "Table"
|
||||
@@ -562,7 +562,7 @@ msgstr "Faqet"
|
||||
|
||||
#: documents/models.py:646
|
||||
msgid "icon"
|
||||
msgstr "ikonë"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:653
|
||||
msgid "sort field"
|
||||
@@ -794,7 +794,7 @@ msgstr "kërkim i thjeshtë teksti"
|
||||
|
||||
#: documents/models.py:742
|
||||
msgid "has duplicates"
|
||||
msgstr "ka dublikatë"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:752
|
||||
msgid "rule type"
|
||||
@@ -882,7 +882,7 @@ msgstr "Fshirje në masë"
|
||||
|
||||
#: documents/models.py:807
|
||||
msgid "Apply AI Suggestions"
|
||||
msgstr "Apliko sugjerimet e AI"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:816 documents/models.py:1468
|
||||
msgid "Scheduled"
|
||||
@@ -1006,23 +1006,23 @@ msgstr "shënime"
|
||||
|
||||
#: documents/models.py:996
|
||||
msgid "page"
|
||||
msgstr "Faqe"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:997
|
||||
msgid "Page of the original file, starting at 1"
|
||||
msgstr "Faqe e skedarit origjinal, duke filluar nga 1"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1052
|
||||
msgid "format"
|
||||
msgstr "Format"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1056
|
||||
msgid "document barcode"
|
||||
msgstr "Dokumenti me barkod"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1057
|
||||
msgid "document barcodes"
|
||||
msgstr "Kodi i barkodit në dokument"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1066
|
||||
msgid "Original"
|
||||
@@ -1404,23 +1404,23 @@ msgstr "Zhvendos në kosh"
|
||||
|
||||
#: documents/models.py:1795
|
||||
msgid "Remote OCR"
|
||||
msgstr "OCR në distancë"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1799
|
||||
msgid "Apply AI suggestions"
|
||||
msgstr "Apliko sugjerimet e IA-së"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1806
|
||||
msgid "Document type"
|
||||
msgstr "Lloji i dokumentit"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1807
|
||||
msgid "Storage path"
|
||||
msgstr "Rruga e ruajtjes"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1808
|
||||
msgid "Created date"
|
||||
msgstr "Data e krijimit"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1811
|
||||
msgid "Workflow Action Type"
|
||||
@@ -1573,27 +1573,27 @@ msgstr "Fjalëkalimet që provohen gjatë heqjes së mbrojtjes PDF. Ndaji me pre
|
||||
|
||||
#: documents/models.py:2048
|
||||
msgid "AI suggestion fields"
|
||||
msgstr "Fushat e sugjerimeve të AI"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:2052
|
||||
msgid "Which of the AI-suggested fields to apply to the document."
|
||||
msgstr "Cilat nga fushat e sugjeruara nga AI t'i aplikoni në dokument."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:2057
|
||||
msgid "create missing objects"
|
||||
msgstr "krijo objektet e munguara"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:2060
|
||||
msgid "Create suggested tags, correspondents, document types and storage paths that do not already exist instead of skipping them."
|
||||
msgstr "Krijoni etiketa të sugjeruara, korrespondentë, lloje dokumentesh dhe shtigje ruajtjeje që nuk ekzistojnë ende, në vend që t'i anashkaloni."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:2066
|
||||
msgid "overwrite existing values"
|
||||
msgstr "mbishkruaj vlera ekzistuese"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:2069
|
||||
msgid "Apply suggestions even if the document already has a value for that field. Tags are always added to, never replaced."
|
||||
msgstr "Zbato sugjerimet edhe nëse dokumenti tashmë ka një vlerë për atë fushë. Etiketat gjithmonë shtohen, kurrë nuk zëvendësohen."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:2075
|
||||
msgid "workflow action"
|
||||
@@ -1605,7 +1605,7 @@ msgstr "veprime rrjedhe pune"
|
||||
|
||||
#: documents/models.py:2091
|
||||
msgid "triggers"
|
||||
msgstr "shkaktarë"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:2098
|
||||
msgid "actions"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "ekzekutime rrjedhash pune"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Leje të pamjaftueshme."
|
||||
@@ -1662,7 +1662,7 @@ msgstr "Fusha e personalizuar me ID %(id)s nuk ekziston"
|
||||
|
||||
#: documents/serialisers.py:2437 documents/serialisers.py:2447
|
||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||
msgstr "Fushat e personalizuara duhet të jenë listë numrash të plotë ose objekt që lidh ID-të me vlerat"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2442
|
||||
msgid "Some custom fields don't exist or were specified twice."
|
||||
@@ -1676,14 +1676,14 @@ msgstr "U zbulua variabël e pavlefshme."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Identifikuesit dublikatë të dokumenteve nuk lejohen."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumentet nuk u gjetën: %(ids)s"
|
||||
|
||||
#: documents/templates/account/account_inactive.html:5
|
||||
msgid "Paperless-ngx account inactive"
|
||||
msgstr "Llogaria Paperless-ngx është joaktive."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/account/account_inactive.html:9
|
||||
msgid "Account inactive."
|
||||
@@ -1807,7 +1807,7 @@ msgstr "Ndrysho fjalëkalimin tim"
|
||||
|
||||
#: documents/templates/account/password_reset_from_key_done.html:5
|
||||
msgid "Paperless-ngx reset password complete"
|
||||
msgstr "Rivendosja e fjalëkalimit për Paperless-ngx përfundoi me sukses."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/account/password_reset_from_key_done.html:9
|
||||
msgid "Password reset complete."
|
||||
@@ -1881,20 +1881,20 @@ msgstr "Lidhja e ndarjes ka skaduar."
|
||||
#: documents/templates/socialaccount/authentication_error.html:5
|
||||
#: documents/templates/socialaccount/login.html:5
|
||||
msgid "Paperless-ngx social account sign in"
|
||||
msgstr "Hyrje në llogarinë sociale të Paperless-ngx"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/socialaccount/authentication_error.html:10
|
||||
msgid "An error occurred while attempting to login via your social network account. Back to the"
|
||||
msgstr "Ndodhi një gabim gjatë përpjekjes për t'u identifikuar përmes llogarisë tuaj të rrjetit social. Kthehu tek"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/socialaccount/authentication_error.html:10
|
||||
msgid "login page"
|
||||
msgstr "faqja e hyrjes"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/socialaccount/login.html:11
|
||||
#, python-format
|
||||
msgid "You are about to connect a new third-party account from %(provider)s."
|
||||
msgstr "Jeni duke u lidhur me një llogari të re të palës së tretë nga %(provider)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/socialaccount/login.html:15
|
||||
msgid "Continue"
|
||||
@@ -1915,64 +1915,64 @@ msgstr "Si hap i fundit, ju lutemi plotësoni formularin e mëposhtëm:"
|
||||
|
||||
#: documents/validators.py:36
|
||||
msgid ", "
|
||||
msgstr " ,"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Specifikoni vetëm njërën prej: text, title_search, query, ose more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "ID e pavlefshme e \"më shumë ngjashmëri\""
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Konfigurim AI i pavlefshëm."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Kërkesa në backend-in e AI-së ka skaduar kohën."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "Backend-i i AI-së refuzoi kërkesën. Kontrolloni regjistrimet për detaje."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Leje të pamjaftueshme për të ndarë dokumentin %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paketa po përpunohet tashmë."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Paketa e lidhjes së ndarjes ende po përgatitet. Ju lutemi provoni përsëri më vonë."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Paketa e lidhjes së ndarjes nuk është e disponueshme."
|
||||
|
||||
#: paperless/apps.py:11
|
||||
msgid "Paperless"
|
||||
msgstr "Paperless"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:26
|
||||
msgid "pdf"
|
||||
msgstr "pdf"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:27
|
||||
msgid "pdfa"
|
||||
msgstr "pdfa"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:28
|
||||
msgid "pdfa-1"
|
||||
msgstr "pdfa-1"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:29
|
||||
msgid "pdfa-2"
|
||||
msgstr "pdfa-2"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:30
|
||||
msgid "pdfa-3"
|
||||
@@ -2004,11 +2004,11 @@ msgstr "kurrë"
|
||||
|
||||
#: paperless/models.py:60
|
||||
msgid "clean"
|
||||
msgstr "pastro"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:61
|
||||
msgid "clean-final"
|
||||
msgstr "versioni përfundimtar i pastruar"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:62
|
||||
msgid "none"
|
||||
@@ -2016,7 +2016,7 @@ msgstr "asnjë"
|
||||
|
||||
#: paperless/models.py:70
|
||||
msgid "LeaveColorUnchanged"
|
||||
msgstr "LëreNgjyrënTëPandryshuar"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:71
|
||||
msgid "RGB"
|
||||
@@ -2024,7 +2024,7 @@ msgstr "RGB"
|
||||
|
||||
#: paperless/models.py:72
|
||||
msgid "UseDeviceIndependentColor"
|
||||
msgstr "PërdorNgjyrën e Pavarur nga Pajisjet"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:73
|
||||
msgid "Gray"
|
||||
@@ -2036,31 +2036,31 @@ msgstr "CMYK"
|
||||
|
||||
#: paperless/models.py:82
|
||||
msgid "Azure AI Document Intelligence"
|
||||
msgstr "Inteligjenca e Dokumenteve Azure AI"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:90
|
||||
msgid "All supported documents"
|
||||
msgstr "Të gjitha dokumentet e mbështetura"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:91
|
||||
msgid "Only when a workflow enables it"
|
||||
msgstr "Vetëm kur një rrjedhë pune e lejon"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:95 paperless/models.py:105
|
||||
msgid "OpenAI-compatible"
|
||||
msgstr "E pajtueshme me OpenAI"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:96
|
||||
msgid "Huggingface"
|
||||
msgstr "Huggingface"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:97 paperless/models.py:106
|
||||
msgid "Ollama"
|
||||
msgstr "Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:115
|
||||
msgid "Sets the output PDF type"
|
||||
msgstr "Cakton llojin e PDF-së së daljes"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:127
|
||||
msgid "Do OCR from page 1 to this value"
|
||||
@@ -2096,7 +2096,7 @@ msgstr "Aktivizon rrotullimin e faqeve"
|
||||
|
||||
#: paperless/models.py:178
|
||||
msgid "Sets the threshold for rotation of pages"
|
||||
msgstr "Vendos pragun për rrotullimin e faqeve"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:184
|
||||
msgid "Sets the maximum image size for decompression"
|
||||
@@ -2104,11 +2104,11 @@ msgstr "Cakton madhësinë maksimale të figurës për dekompresim"
|
||||
|
||||
#: paperless/models.py:190
|
||||
msgid "Sets the Ghostscript color conversion strategy"
|
||||
msgstr "Përcakton strategjinë e konvertimit të ngjyrave për Ghostscript"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:198
|
||||
msgid "Adds additional user arguments for OCRMyPDF"
|
||||
msgstr "Shton argumente shtesë të përdoruesit për OCRMyPDF"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:207
|
||||
msgid "Application title"
|
||||
@@ -2120,7 +2120,7 @@ msgstr "Logoja e aplikacionit"
|
||||
|
||||
#: paperless/models.py:229
|
||||
msgid "Enables barcode scanning"
|
||||
msgstr "Mundëson skanimin e kodeve të vijëzuara"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:235
|
||||
msgid "Enables barcode TIFF support"
|
||||
@@ -2132,19 +2132,19 @@ msgstr "Cakton vargun e barkodit"
|
||||
|
||||
#: paperless/models.py:249
|
||||
msgid "Retains split pages"
|
||||
msgstr "Ruan faqet e ndara"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:255
|
||||
msgid "Enables ASN barcode"
|
||||
msgstr "Aktivizon barkodin ASN"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:261
|
||||
msgid "Sets the ASN barcode prefix"
|
||||
msgstr "Vendos prefiksin e barkodit ASN"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:269
|
||||
msgid "Sets the barcode upscale factor"
|
||||
msgstr "Vendos faktorin e zmadhimit të barkodit"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:276
|
||||
msgid "Sets the barcode DPI"
|
||||
@@ -2152,11 +2152,11 @@ msgstr "Cakton DPI-në e barkodit"
|
||||
|
||||
#: paperless/models.py:283
|
||||
msgid "Sets the maximum pages for barcode"
|
||||
msgstr "Vendos numrin maksimal të faqeve për barkodin"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:290
|
||||
msgid "Enables tag barcode"
|
||||
msgstr "Aktivizon barkodin e etiketës"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:296
|
||||
msgid "Sets the tag barcode mapping"
|
||||
@@ -2168,23 +2168,23 @@ msgstr "Mundëson ndarjen bazuar në barkodet e etiketave"
|
||||
|
||||
#: paperless/models.py:308
|
||||
msgid "Stores the values of detected barcodes"
|
||||
msgstr "Ruan vlerat e barkodeve të zbuluar"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:318
|
||||
msgid "Sets the remote OCR engine"
|
||||
msgstr "Cakton motorin OCR të largët"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:327
|
||||
msgid "Sets the remote OCR API key"
|
||||
msgstr "Cakton çelësin e API-së së OCR-it në distancë"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:335
|
||||
msgid "Sets the remote OCR endpoint"
|
||||
msgstr "Cakton pikën e destinacionit të largët të OCR-së"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:343
|
||||
msgid "Sets which documents are sent to the remote OCR engine"
|
||||
msgstr "Cakton se cilat dokumente dërgohen te motori OCR i largët"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:355
|
||||
msgid "Enables AI features"
|
||||
@@ -2192,23 +2192,23 @@ msgstr "Aktivizon veçoritë AI"
|
||||
|
||||
#: paperless/models.py:360
|
||||
msgid "Sets the LLM embedding backend"
|
||||
msgstr "Vendos mekanizmin e pasme (backend) për embedding-un e LLM-së"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:368
|
||||
msgid "Sets the LLM embedding model"
|
||||
msgstr "Vendos modelin e ngulitjes (embedding) të LLM-së"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:375
|
||||
msgid "Sets the LLM embedding API key"
|
||||
msgstr "Cakton çelësin e API-së së integrimit të LLM-së"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:382
|
||||
msgid "Sets the LLM embedding endpoint, optional"
|
||||
msgstr "Vendos pikën përfundimtare të \"embedding\"-ut për LLM-në (opsionale)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:389
|
||||
msgid "Sets the LLM embedding chunk size"
|
||||
msgstr "Vendos madhësinë e segmentit të enkodimit (embedding) për LLM-në."
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:395
|
||||
msgid "Sets the LLM context size"
|
||||
@@ -2216,7 +2216,7 @@ msgstr "Cakton madhësinë e kontekstit LLM"
|
||||
|
||||
#: paperless/models.py:401
|
||||
msgid "Sets the LLM backend"
|
||||
msgstr "Vendos backend-in e LLM-së"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:409
|
||||
msgid "Sets the LLM model"
|
||||
@@ -2228,7 +2228,7 @@ msgstr "Cakton çelësin API të LLM"
|
||||
|
||||
#: paperless/models.py:423
|
||||
msgid "Sets the LLM endpoint, optional"
|
||||
msgstr "Cakton endpoint-in e embedding LLM, opsionale"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/models.py:430
|
||||
msgid "Sets the LLM output language"
|
||||
@@ -2240,7 +2240,7 @@ msgstr "Cakton kohën limit të LLM në sekonda"
|
||||
|
||||
#: paperless/models.py:443
|
||||
msgid "paperless application settings"
|
||||
msgstr "cilësimet e aplikimit pa përdorim letre"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "English (US)"
|
||||
@@ -2252,7 +2252,7 @@ msgstr "Arabisht"
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Afrikaans"
|
||||
msgstr "Afrikanisht"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Belarusian"
|
||||
@@ -2264,7 +2264,7 @@ msgstr "Bullgarisht"
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Catalan"
|
||||
msgstr "Katalanisht"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "Czech"
|
||||
@@ -2308,7 +2308,7 @@ msgstr "Hungarisht"
|
||||
|
||||
#: paperless/settings/__init__.py:578
|
||||
msgid "Indonesian"
|
||||
msgstr "Indonezisht"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:579
|
||||
msgid "Italian"
|
||||
@@ -2376,7 +2376,7 @@ msgstr "Turqisht"
|
||||
|
||||
#: paperless/settings/__init__.py:595
|
||||
msgid "Ukrainian"
|
||||
msgstr "Ukrainisht"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:596
|
||||
msgid "Vietnamese"
|
||||
@@ -2528,7 +2528,7 @@ msgstr "Process full Email (with embedded attachments in skedar) as .eml"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Process full Mail (with embedded attachments in file) as .eml + process attachments as separate documents"
|
||||
msgstr "Përpuno email-in e plotë (me bashkëngjitje të përfshira në skedar) si .eml + përpuno bashkëngjitjet si dokumente të veçanta."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:116
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
@@ -2564,7 +2564,7 @@ msgstr "Zhvendos në dosjen e specifikuar"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Shënoji si të lexuara, mos i përpuno emailet e lexuara"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:129
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Serbian (Latin)\n"
|
||||
"Language: sr_CS\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "pokretanje tokova rada"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Nedovoljne dozvole."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Otkrivena je nevažeća promenljiva."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Duplirani identifikatori dokumenata nisu dozvoljeni."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumenti nisu pronađeni: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Kao poslednji korak, molimo Vas da popunite sledeći obrazac:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Navedite samo jedno od sledećeg: text, title_search, query ili more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Nevažeći more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Nevažeća konfiguracija veštačke inteligencije."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Navedite samo jedno od sledećeg: text, title_search, query ili more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Nedovoljne dozvole za deljenje dokumenta %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paket se već obrađuje."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Paket linkova za deljenje se još uvek priprema. Molimo pokušajte ponovo kasnije."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Paket linkova za deljenje nije dostupan."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Language: sv_SE\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "arbetsflöde körs"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Otillräckliga behörigheter."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Ogiltig variabel upptäckt."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Dubbletter av dokumentidentifierare är inte tillåtna."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dokumenten hittades inte: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Slutför följande formulär som ett sista steg:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Ange endast ett av text, title_search, query och more_like_id."
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Ogiltig more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Ogiltig AI-konfiguration."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Begäran till AI-serverdelen tog för lång tid."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "AI-serverdelen avvisade begäran. Kontrollera loggarna för detaljer."
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Ange endast ett av text, title_search, query och more_like_id."
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Otillräckliga behörigheter för att dela dokument %(id)s."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paketet behandlas redan."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Delningslänkpaketet förbereds fortfarande. Försök igen senare."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Delningslänkpaketet är inte tillgängligt."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Thai\n"
|
||||
"Language: th_TH\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "ตรวจพบตัวแปรไม่ถูกต้อง"
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:20\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr_TR\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "iş akışı çalıştırma"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Yetersiz izin."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Geçersiz değişken algılandı."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Doküman kimliği çakışmasına izin verilmiyor."
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Dosyalar bulunamadı: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Son adım olarak, lütfen aşağıdaki formu doldurun:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Yalnızca bir metin, title_search, sorgu veya more_like_id"
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "Geçersiz more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "Geçersiz yapay zeka konfigürasyonu."
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "Yapay zeka isteği zaman aşımına uğradı."
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "Yalnızca bir metin, title_search, sorgu veya more_like_id"
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "%(id)s dosyasını paylaşmak için yetkiniz yok."
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Paket zaten işlemede."
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:20\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Ukrainian\n"
|
||||
"Language: uk_UA\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Виявлено неправильну змінну."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr ""
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Як останній крок, будь ласка, заповніть
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:20\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Vietnamese\n"
|
||||
"Language: vi_VN\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "các lần chạy quy trình"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "Không có đủ quyền."
|
||||
@@ -1676,7 +1676,7 @@ msgstr "Đã phát hiện biến số không hợp lệ."
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "Không cho phép các định danh tài liệu trùng lặp"
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "Không tìm thấy tài liệu: %(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "Là bước cuối cùng, vui lòng hoàn thành mẫu đơn sau:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "Không đủ quyền để chia sẻ tài liệu %(id)s"
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "Gói đang được xử lý"
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "Liên kết chia sẻ gói vẫn đang được chuẩn bị. Vui lòng thử lại sau."
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "Không tồn tại liên kết chia sẻ gói"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "工作流运行"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "权限不足。"
|
||||
@@ -1676,7 +1676,7 @@ msgstr "检测到无效变量。"
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "不允许重复文档标识符。"
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "未找到文档:%(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "作为最后一步,请填写以下表格:"
|
||||
msgid ", "
|
||||
msgstr ","
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "只能指定 text、title_search、query 或 more_like_id 中的一项。"
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "无效的more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "无效的 AI 配置。"
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "AI 后端请求超时。"
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "AI 后端拒绝了请求。请检查日志的详细信息。"
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "只能指定 text、title_search、query 或 more_like_id 中的一项。"
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "权限不足以分享文件:%(id)s。"
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "捆绑包已在处理中。"
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "分享链接包仍在准备中,请稍后再试。"
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "分享链接包不可用。"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-10-14 15:18+0000\n"
|
||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
||||
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Traditional\n"
|
||||
"Language: zh_TW\n"
|
||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
||||
msgstr "執行工作流程"
|
||||
|
||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
||||
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||
#: paperless_mail/serialisers.py:156
|
||||
msgid "Insufficient permissions."
|
||||
msgstr "權限不足。"
|
||||
@@ -1676,7 +1676,7 @@ msgstr "偵測到無效的變數。"
|
||||
msgid "Duplicate document identifiers are not allowed."
|
||||
msgstr "文件識別碼不得重複。"
|
||||
|
||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
||||
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||
#, python-format
|
||||
msgid "Documents not found: %(ids)s"
|
||||
msgstr "找不到文件:%(ids)s"
|
||||
@@ -1917,40 +1917,40 @@ msgstr "最後一步,請填寫以下表單:"
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: documents/views.py:318
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "只能指定 text、title_search、query 或 more_like_id 的其中一項。"
|
||||
|
||||
#: documents/views.py:356 documents/views.py:2765
|
||||
#: documents/views.py:337 documents/views.py:2749
|
||||
msgid "Invalid more_like_id"
|
||||
msgstr "無效的 more_like_id"
|
||||
|
||||
#: documents/views.py:1703
|
||||
#: documents/views.py:1684
|
||||
msgid "Invalid AI configuration."
|
||||
msgstr "無效的 AI 配置。"
|
||||
|
||||
#: documents/views.py:1714
|
||||
#: documents/views.py:1695
|
||||
msgid "AI backend request timed out."
|
||||
msgstr "AI 後端請求逾時。"
|
||||
|
||||
#: documents/views.py:1726
|
||||
#: documents/views.py:1707
|
||||
msgid "AI backend rejected the request. Check logs for details."
|
||||
msgstr "AI 後端拒絕了請求。請檢查日誌以取得詳細資訊。"
|
||||
|
||||
#: documents/views.py:4827
|
||||
#: documents/views.py:2574 documents/views.py:2890
|
||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||
msgstr "只能指定 text、title_search、query 或 more_like_id 的其中一項。"
|
||||
|
||||
#: documents/views.py:4815
|
||||
#, python-format
|
||||
msgid "Insufficient permissions to share document %(id)s."
|
||||
msgstr "權限不足,無法共用文件:%(id)s"
|
||||
|
||||
#: documents/views.py:4874
|
||||
#: documents/views.py:4862
|
||||
msgid "Bundle is already being processed."
|
||||
msgstr "組合已在處理中。"
|
||||
|
||||
#: documents/views.py:4938
|
||||
#: documents/views.py:4926
|
||||
msgid "The share link bundle is still being prepared. Please try again later."
|
||||
msgstr "共用連結組合仍在準備中,請稍後再試。"
|
||||
|
||||
#: documents/views.py:4952
|
||||
#: documents/views.py:4940
|
||||
msgid "The share link bundle is unavailable."
|
||||
msgstr "共用連結組合無法使用。"
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ logger = logging.getLogger("paperless.auth")
|
||||
class AutoLoginMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request: HttpRequest) -> None:
|
||||
# Dont use auto-login with token request
|
||||
if request.path_info.startswith("/api/token/") and request.method == "POST":
|
||||
if request.path.startswith("/api/token/") and request.method == "POST":
|
||||
return None
|
||||
try:
|
||||
request.user = User.objects.get(
|
||||
|
||||
@@ -51,21 +51,3 @@ class TestAutoLoginMiddleware(TestCase):
|
||||
self._process(request)
|
||||
|
||||
self.assertFalse(request.user.is_authenticated)
|
||||
|
||||
@override_settings(FORCE_SCRIPT_NAME="/paperless")
|
||||
def test_token_request_is_not_logged_in_under_subpath(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Paperless is served under a subpath
|
||||
WHEN:
|
||||
- A token is requested via POST
|
||||
THEN:
|
||||
- The request is left anonymous so the posted credentials are used
|
||||
"""
|
||||
UserFactory(username="autologin")
|
||||
|
||||
request = self.factory.post("/api/token/")
|
||||
request.user = AnonymousUser()
|
||||
self._process(request)
|
||||
|
||||
self.assertFalse(request.user.is_authenticated)
|
||||
@@ -5,7 +5,7 @@ from django.contrib.auth.models import User
|
||||
|
||||
from documents.models import Document
|
||||
from documents.permissions import permitted_document_ids
|
||||
from documents.permissions import user_is_active_superuser
|
||||
from documents.permissions import user_is_unrestricted
|
||||
from documents.versioning import annotate_effective_content
|
||||
from paperless.config import AIConfig
|
||||
from paperless_ai.base_model import ClassificationSuggestions
|
||||
@@ -50,17 +50,16 @@ def _fulltext_similar_documents(
|
||||
similarity - cruder, but far better than no candidates at all.
|
||||
more_like_this_ids returns only a ranked ID list, no scores, so weight is
|
||||
synthesized from rank (descending from top_k) rather than claiming a
|
||||
similarity magnitude that doesn't exist. An active superuser is
|
||||
normalized to ``None`` (no filtering) before calling, since the backend's
|
||||
permission filter has no superuser short-circuit of its own. ``user=None``
|
||||
means the document is unowned, so results are limited to unowned documents
|
||||
rather than the whole library. Results are re-checked with
|
||||
similarity magnitude that doesn't exist. An unrestricted user (none, or an
|
||||
active superuser - see user_is_unrestricted) is normalized to ``None``
|
||||
before calling, since the backend's permission filter has no superuser
|
||||
short-circuit of its own. Results are re-checked with
|
||||
permitted_document_ids() since Tantivy's indexed permission fields lag the
|
||||
DB via async reindexing and judge a version by its own owner.
|
||||
"""
|
||||
from documents.search import get_backend
|
||||
|
||||
unrestricted = user_is_active_superuser(user)
|
||||
unrestricted = user_is_unrestricted(user)
|
||||
search_user = None if unrestricted else user
|
||||
backend = get_backend()
|
||||
similar_ids = backend.more_like_this_ids(
|
||||
@@ -188,16 +187,13 @@ def get_taxonomy_context(
|
||||
candidates/context rather than propagating the exception - neither a
|
||||
vector-store outage nor a search-index issue should block classification,
|
||||
only its context-assisted enrichment.
|
||||
|
||||
``user=None`` means "acting for an unowned document" (workflows act as the
|
||||
document owner), NOT "no restriction": only unowned documents and taxonomy
|
||||
are considered, so another user's private documents never reach the prompt.
|
||||
"""
|
||||
ai_config = AIConfig()
|
||||
try:
|
||||
if ai_config.llm_embedding_backend:
|
||||
# None means "no restriction" to retrieve_similar_nodes. An active
|
||||
# superuser can see every document, so skip
|
||||
# None means "no restriction" to retrieve_similar_nodes. An
|
||||
# unrestricted user (no user at all, or an active superuser -- see
|
||||
# user_is_unrestricted) can see every document, so skip
|
||||
# materializing every visible pk into a Python list and passing it
|
||||
# through as an IN filter: for a large library that is a wasted
|
||||
# quadratic scan in the vector store at best, and past ~32,763
|
||||
@@ -208,7 +204,7 @@ def get_taxonomy_context(
|
||||
# which documents are considered -- only how we get there.
|
||||
visible_document_ids = (
|
||||
None
|
||||
if user_is_active_superuser(user)
|
||||
if user_is_unrestricted(user)
|
||||
else list(permitted_document_ids(user))
|
||||
)
|
||||
nodes = retrieve_similar_nodes(
|
||||
@@ -230,14 +226,8 @@ def get_taxonomy_context(
|
||||
|
||||
# similar_documents is already ordered by descending weight; don't lose it.
|
||||
similar_document_ids = [s["document_id"] for s in similar_documents]
|
||||
similar_queryset = Document.objects.all()
|
||||
if not user_is_active_superuser(user):
|
||||
# Already scoped by retrieval but re-checked here before being used in the prompt
|
||||
similar_queryset = similar_queryset.filter(
|
||||
id__in=permitted_document_ids(user),
|
||||
)
|
||||
similar_documents_by_id = annotate_effective_content(
|
||||
similar_queryset,
|
||||
Document.objects.all(),
|
||||
).in_bulk(similar_document_ids)
|
||||
similar_docs = [
|
||||
similar_documents_by_id[document_id]
|
||||
|
||||
@@ -12,7 +12,7 @@ from documents.models import DocumentType
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.permissions import permitted_object_ids
|
||||
from documents.permissions import restrict_queryset_to_owner_visible
|
||||
from documents.permissions import restrict_queryset_to_visible
|
||||
|
||||
MATCH_THRESHOLD = 0.8
|
||||
|
||||
@@ -34,7 +34,7 @@ def _resolve_visible_ids(
|
||||
"""
|
||||
if not ids:
|
||||
return []
|
||||
queryset = restrict_queryset_to_owner_visible(
|
||||
queryset = restrict_queryset_to_visible(
|
||||
model.objects.filter(pk__in=ids),
|
||||
user,
|
||||
perm,
|
||||
|
||||
@@ -13,7 +13,7 @@ from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.permissions import restrict_queryset_to_owner_visible
|
||||
from documents.permissions import restrict_queryset_to_visible
|
||||
from paperless_ai.prompts.context import TaxonomyBlockPromptContext
|
||||
from paperless_ai.prompts.render import render_prompt
|
||||
|
||||
@@ -97,7 +97,7 @@ def _visible_ranked_candidates(
|
||||
"""
|
||||
if not weighted_ids:
|
||||
return []
|
||||
visible_queryset = restrict_queryset_to_owner_visible(
|
||||
visible_queryset = restrict_queryset_to_visible(
|
||||
model.objects.filter(pk__in=weighted_ids),
|
||||
user,
|
||||
perm,
|
||||
|
||||
@@ -613,83 +613,32 @@ class TestGetTaxonomyContextVisibility:
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(LLM_EMBEDDING_BACKEND="huggingface")
|
||||
def test_restricts_to_unowned_documents_when_no_user(
|
||||
def test_skips_permission_lookup_when_no_user(
|
||||
self,
|
||||
mocker: pytest_mock.MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- No user is supplied (a workflow acting for an unowned document)
|
||||
- Another user's private document
|
||||
- No user is supplied
|
||||
WHEN:
|
||||
- get_taxonomy_context() is called
|
||||
THEN:
|
||||
- Only unowned documents are passed to retrieve_similar_nodes(),
|
||||
never the whole library
|
||||
- Permission lookup is skipped and no document_ids restriction is
|
||||
passed to retrieve_similar_nodes()
|
||||
"""
|
||||
other_owner = UserFactory.create()
|
||||
document = DocumentFactory.create(content="Some content", owner=None)
|
||||
unowned = DocumentFactory.create(content="Public", owner=None)
|
||||
private = DocumentFactory.create(content="Private", owner=other_owner)
|
||||
document = DocumentFactory.create(content="Some content")
|
||||
mock_retrieve = mocker.patch(
|
||||
"paperless_ai.ai_classifier.retrieve_similar_nodes",
|
||||
return_value=[],
|
||||
)
|
||||
mock_permitted = mocker.patch(
|
||||
"paperless_ai.ai_classifier.permitted_document_ids",
|
||||
)
|
||||
|
||||
get_taxonomy_context(document, None)
|
||||
|
||||
visible = mock_retrieve.call_args.kwargs["document_ids"]
|
||||
assert visible is not None
|
||||
assert document.pk in visible
|
||||
assert unowned.pk in visible
|
||||
assert private.pk not in visible
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(LLM_EMBEDDING_BACKEND="huggingface")
|
||||
def test_private_document_content_never_reaches_context_when_no_user(
|
||||
self,
|
||||
mocker: pytest_mock.MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- No user is supplied (a workflow acting for an unowned document)
|
||||
- Retrieval returns another user's private document anyway (e.g.
|
||||
a stale vector store)
|
||||
WHEN:
|
||||
- get_taxonomy_context() is called
|
||||
THEN:
|
||||
- The private document's title and content are not in the RAG
|
||||
context, while an unowned neighbour's are
|
||||
"""
|
||||
other_owner = UserFactory.create()
|
||||
document = DocumentFactory.create(content="Some content", owner=None)
|
||||
unowned = DocumentFactory.create(
|
||||
title="Public neighbour",
|
||||
content="public text",
|
||||
owner=None,
|
||||
)
|
||||
private = DocumentFactory.create(
|
||||
title="Secret neighbour",
|
||||
content="secret text",
|
||||
owner=other_owner,
|
||||
)
|
||||
mocker.patch(
|
||||
"paperless_ai.ai_classifier.retrieve_similar_nodes",
|
||||
return_value=[],
|
||||
)
|
||||
mocker.patch(
|
||||
"paperless_ai.ai_classifier._node_document_weights",
|
||||
return_value=[
|
||||
SimilarDocument(document_id=private.pk, weight=0.9),
|
||||
SimilarDocument(document_id=unowned.pk, weight=0.5),
|
||||
],
|
||||
)
|
||||
|
||||
_candidates, context = get_taxonomy_context(document, None)
|
||||
|
||||
assert "Public neighbour" in context
|
||||
assert "Secret neighbour" not in context
|
||||
assert "secret text" not in context
|
||||
mock_permitted.assert_not_called()
|
||||
assert mock_retrieve.call_args.kwargs["document_ids"] is None
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(LLM_EMBEDDING_BACKEND="huggingface")
|
||||
@@ -719,9 +668,7 @@ class TestGetTaxonomyContextVisibility:
|
||||
|
||||
get_taxonomy_context(document, user)
|
||||
|
||||
# Once for retrieval, once to re-check the documents whose text goes
|
||||
# into the prompt
|
||||
assert {c.args for c in mock_permitted.call_args_list} == {(user,)}
|
||||
mock_permitted.assert_called_once_with(user)
|
||||
assert mock_retrieve.call_args.kwargs["document_ids"] == [1, 2, 3]
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -930,34 +877,6 @@ class TestFulltextSimilarDocuments:
|
||||
|
||||
assert [s["document_id"] for s in result] == [other.pk]
|
||||
|
||||
def test_no_user_only_returns_unowned_documents(
|
||||
self,
|
||||
fulltext_backend: TantivyBackend,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- An unowned source document, an unowned similar document and a
|
||||
similar document privately owned by another user
|
||||
WHEN:
|
||||
- _fulltext_similar_documents() is called with no user (a workflow
|
||||
acting for an unowned document)
|
||||
THEN:
|
||||
- Only the unowned similar document is returned
|
||||
"""
|
||||
other_owner = UserFactory.create()
|
||||
source = DocumentFactory.create(content="shared content phrase", owner=None)
|
||||
unowned = DocumentFactory.create(content="shared content phrase", owner=None)
|
||||
private = DocumentFactory.create(
|
||||
content="shared content phrase",
|
||||
owner=other_owner,
|
||||
)
|
||||
for doc in (source, unowned, private):
|
||||
fulltext_backend.add_or_update(doc)
|
||||
|
||||
result = _fulltext_similar_documents(source, user=None, top_k=5)
|
||||
|
||||
assert [s["document_id"] for s in result] == [unowned.pk]
|
||||
|
||||
def test_excludes_stale_permitted_document_for_regular_user(
|
||||
self,
|
||||
fulltext_backend: TantivyBackend,
|
||||
|
||||
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user