collective

Browse / github.com/storytold/pdfcraft / patch

M14: an administrator's policy keeps document JavaScript off (#1846)

M14: an administrator's policy keeps document JavaScript off (#1846) ## What this changes This is the JavaScript part of #1846, which #3992 also asks for. Thanks @oracola91 for going through the design on #1846: the key name, the JSON file on Linux, failing closed on a policy that can't be read, and the test for the background open path all come from that discussion. Thanks also to @23z69rzihnk for #3992. An administrator can now turn JavaScript in documents off for everyone on a machine, and people can't turn it back on in Preferences. It's Acrobat's FeatureLockDown `bDisableJavaScript`, under a PdfCraft name: | System | Where | Value | |---|---|---| | Wind

Status
open
Score
0.00
Adopters
0 other operators
Author
jcl-bit (imported from pull request #5054, open)
Base
1889690bfa65c908654cf3fe9a1e5f2f37dcbe02
Size
+1066 −110 in 67 files
Review
pending
Shared
2026-10-28
Id
329743186414ef0105d9365d

How it went

No other operator has reported using it yet.

Diff

raw · ATTRIBUTION.md ATTRIBUTION.toml Cargo.lock README.md ROADMAP.md apps/pdfcraft-cli/src/commands.rs apps/pdfcraft-cli/src/run.rs apps/pdfcraft/src/main.rs crates/automation/src/a11y/documents.rs crates/automation/src/a11y/form_tools.rs crates/automation/src/a11y/ocr.rs crates/automation/src/batch.rs crates/automation/src/lib.rs crates/automation/src/tools.rs crates/automation/tests/it/automation.rs crates/automation/tests/it/batch.rs crates/engine/src/actions.rs crates/engine/src/js.rs crates/engine/src/ocr.rs crates/engine/src/open.rs crates/engine/src/session.rs crates/engine/src/session/editing.rs crates/engine/src/tests.rs crates/platform/Cargo.toml crates/platform/src/lib.rs crates/platform/src/policy.rs crates/ui-egui/src/app/prefs.rs crates/ui-egui/src/i18n/ar.tsv crates/ui-egui/src/i18n/bg.tsv crates/ui-egui/src/i18n/ca.tsv crates/ui-egui/src/i18n/ckb.tsv crates/ui-egui/src/i18n/cs.tsv crates/ui-egui/src/i18n/de.tsv crates/ui-egui/src/i18n/el.tsv crates/ui-egui/src/i18n/es.tsv crates/ui-egui/src/i18n/fa.tsv crates/ui-egui/src/i18n/fi.tsv crates/ui-egui/src/i18n/fr.tsv crates/ui-egui/src/i18n/hi.tsv crates/ui-egui/src/i18n/hu.tsv crates/ui-egui/src/i18n/id.tsv crates/ui-egui/src/i18n/it.tsv crates/ui-egui/src/i18n/ja.tsv crates/ui-egui/src/i18n/kk.tsv crates/ui-egui/src/i18n/ko.tsv crates/ui-egui/src/i18n/lo.tsv crates/ui-egui/src/i18n/lt.tsv crates/ui-egui/src/i18n/nb.tsv crates/ui-egui/src/i18n/pl.tsv crates/ui-egui/src/i18n/pt-br.tsv crates/ui-egui/src/i18n/ro.tsv crates/ui-egui/src/i18n/ru.tsv crates/ui-egui/src/i18n/sl.tsv crates/ui-egui/src/i18n/sq.tsv crates/ui-egui/src/i18n/te.tsv crates/ui-egui/src/i18n/th.tsv crates/ui-egui/src/i18n/tr.tsv crates/ui-egui/src/i18n/uk.tsv crates/ui-egui/src/i18n/vi.tsv crates/ui-egui/src/i18n/zh-hans.tsv crates/ui-egui/src/i18n/zh-hant.tsv crates/ui-egui/src/workflows/actions_ui.rs crates/ui-egui/src/workflows/js_ui.rs crates/ui-egui/src/workflows/ocr_ui.rs crates/ui-egui/tests/it/javascript.rs docs/administrators.md parity/acrobat-features.toml

diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md--- a/ATTRIBUTION.md+++ b/ATTRIBUTION.md@@ -8,34 +8,34 @@ Every asset PdfCraft includes, bundles or uses to build its published material,  | Asset | Title | Author | Licence | Source | Used for | |---|---|---|---|---|---|-| `crates/ui-egui/src/i18n/el.tsv` | Greek interface translation catalog | Camthalion77 and contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft English UI labels; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Greek UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/zh-hans.tsv` | Simplified Chinese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/zh-hant.tsv` | Traditional Chinese (Taiwan) interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/cs.tsv` | Czech interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check and standard-identification wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Czech UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/pt-br.tsv` | Brazilian Portuguese interface translation catalog | André Cardoso (andrecards), PdfCraft contributors | MIT OR Apache-2.0 | Clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources: the ~1,826-entry gist posted in issue #288 (https://gist.github.com/andrecards/ac388c25fba17312ce83207f8b7b467e), completed and reviewed by PdfCraft contributors; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table limit messages are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/es.tsv` | Spanish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; the 2026-10-20 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Spanish UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/fr.tsv` | French interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | French UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/de.tsv` | German interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | German UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/lo.tsv` | Lao interface translation catalog | @bounkirdni-2025 | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Lao UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/te.tsv` | Telugu interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Telugu UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/it.tsv` | Italian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Italian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/pl.tsv` | Polish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Polish UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/ru.tsv` | Russian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Russian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/hu.tsv` | Hungarian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Hungarian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/bg.tsv` | Bulgarian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/kk.tsv` | Kazakh interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of the English UI labels into Kazakh, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Kazakh UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/ja.tsv` | Japanese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Japanese UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/uk.tsv` | Ukrainian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/ar.tsv` | Arabic interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Arabic UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/ckb.tsv` | Central Kurdish (Sorani) interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Central Kurdish UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/fa.tsv` | Persian (Farsi) interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-25 initial catalog covering the current 2,776-entry key union, AI-assisted translations from English, not separately native-reviewed | Persian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/tr.tsv` | Turkish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Turkish UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/lt.tsv` | Lithuanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; original catalog reviewed by a native Lithuanian-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/hi.tsv` | Hindi interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Hindi UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/id.tsv` | Indonesian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Indonesian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/ko.tsv` | Korean interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Korean UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/ro.tsv` | Romanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; AI-assisted and not yet reviewed line by line by a native Romanian speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Romanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/fi.tsv` | Finnish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: AI-assisted (Claude Code) clean-room translations of PdfCraft's English UI labels and user-facing messages; not yet proofread by a native Finnish speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 calculation-order, text-format, print, thumbnail, layer, comment-reshape and signature labels (previously English placeholders) are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Finnish UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/el.tsv` | Greek interface translation catalog | Camthalion77 and contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft English UI labels; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Greek UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/zh-hans.tsv` | Simplified Chinese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/zh-hant.tsv` | Traditional Chinese (Taiwan) interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/cs.tsv` | Czech interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check and standard-identification wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Czech UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/pt-br.tsv` | Brazilian Portuguese interface translation catalog | André Cardoso (andrecards), PdfCraft contributors | MIT OR Apache-2.0 | Clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources: the ~1,826-entry gist posted in issue #288 (https://gist.github.com/andrecards/ac388c25fba17312ce83207f8b7b467e), completed and reviewed by PdfCraft contributors; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table limit messages are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/es.tsv` | Spanish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; the 2026-10-20 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Spanish UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/fr.tsv` | French interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | French UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/de.tsv` | German interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | German UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/lo.tsv` | Lao interface translation catalog | @bounkirdni-2025 | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Lao UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/te.tsv` | Telugu interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Telugu UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/it.tsv` | Italian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Italian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/pl.tsv` | Polish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Polish UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/ru.tsv` | Russian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Russian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/hu.tsv` | Hungarian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Hungarian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/bg.tsv` | Bulgarian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/kk.tsv` | Kazakh interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of the English UI labels into Kazakh, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Kazakh UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/ja.tsv` | Japanese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Japanese UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/uk.tsv` | Ukrainian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/ar.tsv` | Arabic interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Arabic UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/ckb.tsv` | Central Kurdish (Sorani) interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Central Kurdish UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/fa.tsv` | Persian (Farsi) interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-25 initial catalog covering the current 2,776-entry key union, AI-assisted translations from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Persian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/tr.tsv` | Turkish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Turkish UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/lt.tsv` | Lithuanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; original catalog reviewed by a native Lithuanian-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/hi.tsv` | Hindi interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Hindi UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/id.tsv` | Indonesian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Indonesian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/ko.tsv` | Korean interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Korean UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/ro.tsv` | Romanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; AI-assisted and not yet reviewed line by line by a native Romanian speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Romanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/fi.tsv` | Finnish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: AI-assisted (Claude Code) clean-room translations of PdfCraft's English UI labels and user-facing messages; not yet proofread by a native Finnish speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 calculation-order, text-format, print, thumbnail, layer, comment-reshape and signature labels (previously English placeholders) are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Finnish UI strings, compiled in by crates/ui-egui/src/i18n.rs | | `assets/icons/accessibility.svg` | Lucide icon "accessibility" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/accessibility.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs | | `assets/icons/align-left.svg` | Lucide icon "align-left" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/align-left.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs | | `assets/icons/align-center.svg` | Lucide icon "align-center" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/align-center.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |@@ -286,17 +286,17 @@ Every asset PdfCraft includes, bundles or uses to build its published material, | `packaging/macos/dmg/background.tiff` | PdfCraft macOS DMG window background (rendered) | @XusBadia | MIT OR Apache-2.0 | Rendered from packaging/macos/dmg/background.svg by packaging/macos/dmg/generate.py | 1x + 2x HiDPI 16-colour palette TIFF copied into the macOS DMG by packaging/macos/package.sh; includes the app icon and the free LittleCMS built-in sRGB profile | | `crates/sign/tests/data/x509-rsa-sha1.pdf` | One-page PDF with a legacy adbe.x509.rsa_sha1 signature by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl dgst -sha1 -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/it/pdf.rs): the legacy adbe.x509.rsa_sha1 format | | `assets/icons/monitor.svg` | Lucide icon "monitor" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/monitor.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |-| `crates/ui-egui/src/i18n/vi.tsv` | Vietnamese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels; PhotoCraft and VectorCraft Vietnamese catalogs used only as translation memory when English source and semantic context matched; original catalog reviewed by a native Vietnamese-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/sl.tsv` | Slovene interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Slovene UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/vi.tsv` | Vietnamese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels; PhotoCraft and VectorCraft Vietnamese catalogs used only as translation memory when English source and semantic context matched; original catalog reviewed by a native Vietnamese-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/sl.tsv` | Slovene interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Slovene UI strings, compiled in by crates/ui-egui/src/i18n.rs | | `vendor/vello_common/assets/probe.png` | vello_common probe reference image (rendered from probe.rgba) | Linebender vello contributors | MIT OR Apache-2.0 | vello_common 0.0.8 (crates.io), vendored in vendor/vello_common; regenerated by its vello_sparse_tests regenerate_probe_reference binary | Reference picture of the vendored vello_common probe module, for people only; the build reads probe.rgba, not this file |-| `crates/ui-egui/src/i18n/nb.tsv` | Norwegian Bokmål interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Norwegian Bokmål UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/nb.tsv` | Norwegian Bokmål interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Norwegian Bokmål UI strings, compiled in by crates/ui-egui/src/i18n.rs | | `assets/icons/superscript.svg` | Lucide icon "superscript" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/superscript.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs | | `assets/icons/subscript.svg` | Lucide icon "subscript" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/subscript.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs | | `assets/icons/pilcrow.svg` | Lucide icon "pilcrow" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/pilcrow.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs | | `assets/icons/move-horizontal.svg` | Lucide icon "move-horizontal" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/move-horizontal.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |-| `crates/ui-egui/src/i18n/sq.tsv` | Albanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-appearance labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Albanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/th.tsv` | Thai interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Thai UI strings, compiled in by crates/ui-egui/src/i18n.rs |-| `crates/ui-egui/src/i18n/ca.tsv` | Catalan interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, written from their meaning; no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Catalan UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/sq.tsv` | Albanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-appearance labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Albanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/th.tsv` | Thai interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Thai UI strings, compiled in by crates/ui-egui/src/i18n.rs |+| `crates/ui-egui/src/i18n/ca.tsv` | Catalan interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, written from their meaning; no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed | Catalan UI strings, compiled in by crates/ui-egui/src/i18n.rs |  ## Compiled in through dependencies (6) diff --git a/ATTRIBUTION.toml b/ATTRIBUTION.toml--- a/ATTRIBUTION.toml+++ b/ATTRIBUTION.toml@@ -2,7 +2,7 @@ path = "crates/ui-egui/src/i18n/el.tsv" title = "Greek interface translation catalog" author = "Camthalion77 and contributors"-source = "Contributor-original: clean-room translations of PdfCraft English UI labels; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft English UI labels; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -26,7 +26,7 @@ usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/zh-hans.tsv" title = "Simplified Chinese interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -36,7 +36,7 @@ usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n.r path = "crates/ui-egui/src/i18n/zh-hant.tsv" title = "Traditional Chinese (Taiwan) interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -46,7 +46,7 @@ usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n. path = "crates/ui-egui/src/i18n/cs.tsv" title = "Czech interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check and standard-identification wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check and standard-identification wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -56,7 +56,7 @@ usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/pt-br.tsv" title = "Brazilian Portuguese interface translation catalog" author = "André Cardoso (andrecards), PdfCraft contributors"-source = "Clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources: the ~1,826-entry gist posted in issue #288 (https://gist.github.com/andrecards/ac388c25fba17312ce83207f8b7b467e), completed and reviewed by PdfCraft contributors; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table limit messages are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources: the ~1,826-entry gist posted in issue #288 (https://gist.github.com/andrecards/ac388c25fba17312ce83207f8b7b467e), completed and reviewed by PdfCraft contributors; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table limit messages are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -66,7 +66,7 @@ usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n path = "crates/ui-egui/src/i18n/es.tsv" title = "Spanish interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; the 2026-10-20 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; the 2026-10-20 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -76,7 +76,7 @@ usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/fr.tsv" title = "French interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -86,7 +86,7 @@ usage = "French UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/de.tsv" title = "German interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -96,7 +96,7 @@ usage = "German UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/lo.tsv" title = "Lao interface translation catalog" author = "@bounkirdni-2025"-source = "Contributor-original: clean-room translations of PdfCraft English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -106,7 +106,7 @@ usage = "Lao UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/te.tsv" title = "Telugu interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -116,7 +116,7 @@ usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/it.tsv" title = "Italian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -126,7 +126,7 @@ usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/pl.tsv" title = "Polish interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -136,7 +136,7 @@ usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/ru.tsv" title = "Russian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -146,7 +146,7 @@ usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/hu.tsv" title = "Hungarian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -156,7 +156,7 @@ usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/bg.tsv" title = "Bulgarian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -166,7 +166,7 @@ usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/kk.tsv" title = "Kazakh interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of the English UI labels into Kazakh, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of the English UI labels into Kazakh, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -176,7 +176,7 @@ usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/ja.tsv" title = "Japanese interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -186,7 +186,7 @@ usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/uk.tsv" title = "Ukrainian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -196,7 +196,7 @@ usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/ar.tsv" title = "Arabic interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -206,7 +206,7 @@ usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/ckb.tsv" title = "Central Kurdish (Sorani) interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -216,7 +216,7 @@ usage = "Central Kurdish UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/fa.tsv" title = "Persian (Farsi) interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-25 initial catalog covering the current 2,776-entry key union, AI-assisted translations from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-25 initial catalog covering the current 2,776-entry key union, AI-assisted translations from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -226,7 +226,7 @@ usage = "Persian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/tr.tsv" title = "Turkish interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -236,7 +236,7 @@ usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/lt.tsv" title = "Lithuanian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; original catalog reviewed by a native Lithuanian-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; original catalog reviewed by a native Lithuanian-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -246,7 +246,7 @@ usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/hi.tsv" title = "Hindi interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -255,7 +255,7 @@ usage = "Hindi UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/id.tsv" title = "Indonesian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -264,7 +264,7 @@ usage = "Indonesian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/ko.tsv" title = "Korean interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -274,7 +274,7 @@ usage = "Korean UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/ro.tsv" title = "Romanian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; AI-assisted and not yet reviewed line by line by a native Romanian speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; AI-assisted and not yet reviewed line by line by a native Romanian speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -285,7 +285,7 @@ usage = "Romanian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/fi.tsv" title = "Finnish interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: AI-assisted (Claude Code) clean-room translations of PdfCraft's English UI labels and user-facing messages; not yet proofread by a native Finnish speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 calculation-order, text-format, print, thumbnail, layer, comment-reshape and signature labels (previously English placeholders) are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: AI-assisted (Claude Code) clean-room translations of PdfCraft's English UI labels and user-facing messages; not yet proofread by a native Finnish speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 calculation-order, text-format, print, thumbnail, layer, comment-reshape and signature labels (previously English placeholders) are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -3418,7 +3418,7 @@ sha256 = "d1a443233345724859de8e4fb48968ba91e885e12ddbd710701548bd91b39428" path = "crates/ui-egui/src/i18n/vi.tsv" title = "Vietnamese interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels; PhotoCraft and VectorCraft Vietnamese catalogs used only as translation memory when English source and semantic context matched; original catalog reviewed by a native Vietnamese-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels; PhotoCraft and VectorCraft Vietnamese catalogs used only as translation memory when English source and semantic context matched; original catalog reviewed by a native Vietnamese-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -3428,7 +3428,7 @@ usage = "Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/sl.tsv" title = "Slovene interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -3450,7 +3450,7 @@ sha256 = "78c59a0cfd2c829909d77006f94e34dac1ae5d1f90e7f10f72e84de541fef697" path = "crates/ui-egui/src/i18n/nb.tsv" title = "Norwegian Bokmål interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -3504,7 +3504,7 @@ sha256 = "864ab2f19254e6ac7240bc6a98323197332e8f714d4d3f94831f41309e5ef2f7" path = "crates/ui-egui/src/i18n/sq.tsv" title = "Albanian interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-appearance labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-appearance labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -3514,7 +3514,7 @@ usage = "Albanian UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/th.tsv" title = "Thai interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"@@ -3524,7 +3524,7 @@ usage = "Thai UI strings, compiled in by crates/ui-egui/src/i18n.rs" path = "crates/ui-egui/src/i18n/ca.tsv" title = "Catalan interface translation catalog" author = "PdfCraft contributors"-source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, written from their meaning; no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"+source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, written from their meaning; no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed; the 2026-10-27 administrator-policy JavaScript notes are AI-assisted translations from English, not separately native-reviewed" licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation"diff --git a/Cargo.lock b/Cargo.lock--- a/Cargo.lock+++ b/Cargo.lock@@ -4380,7 +4380,10 @@ version = "0.14.0" dependencies = [  "async-channel",  "futures-lite",+ "objc2-core-foundation",  "rustix 1.1.5",+ "serde_json",+ "winsafe",  "zbus", ] diff --git a/README.md b/README.md--- a/README.md+++ b/README.md@@ -495,6 +495,9 @@ and command-line tool are signed with the Developer ID `Learning Machines LLC (D notarized. The MSI's manufacturer, shown as the publisher in Installed apps, is `Learning Machines LLC`. +To turn JavaScript in documents off across a fleet (Group Policy, Intune, an RMM, an MDM profile+on macOS or `/etc` on Linux), see [Policies for administrators](docs/administrators.md).+ ### macOS  | Build | File | Notes |diff --git a/ROADMAP.md b/ROADMAP.md--- a/ROADMAP.md+++ b/ROADMAP.md@@ -200,6 +200,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md  ## Progress log +- **2026-10-27 (M14, #1846, `misc.admin-preferences` planned → partial):** An administrator's policy can turn document JavaScript off and keep it off (Acrobat's `bDisableJavaScript`): `DisableJavaScript` under `HKLM` or `HKCU\SOFTWARE\Policies\PdfCraft`, in managed preferences for `ai.storyteller.pdfcraft`, or in `/etc/pdfcraft/policy.json`. The app and `pdfcraft-cli` read it once at launch; Preferences shows the box locked with where the policy is, and the person's own choice is kept for when the policy goes. A policy that can't be read keeps JavaScript off and says why. The background open path (#1493) takes the effective setting, so XFA open scripts stay off there too. The Action Wizard, multi-file OCR and `doc_batch`, which process each file in a session of their own, now take the caller's setting: before, they ran a file's scripts (and an action's "Run a JavaScript" step) even with JavaScript turned off in Preferences. `pdfcraft-platform::policy` reads it with crates already in the tree (winsafe, objc2-core-foundation, serde_json). Guide: [docs/administrators.md](docs/administrators.md). Still missing: the other keys in #1846, ADMX templates, the Flatpak build. Estimates unchanged. - **2026-10-27 (M10, OCR models):** Model staging downloads and verifies the ONNX pair after the upstream RTEN URL brownout; runtime discovery prefers complete ONNX pairs per directory, retains complete RTEN fallback and preserves directory precedence. - **2026-10-24 (M10, #4158 partial):** Word, HTML, RTF and PowerPoint export keep a block that continues another on its line (an italic case name, a link, a superscript) as a run in its own style inside that paragraph, with the source's word space, instead of a paragraph of its own. Style changes that wrap onto the next line still split paragraphs (the reading blocks keep style boundaries). Estimates unchanged. - **2026-10-24 (M3, window move on Wayland, #3970 follow-up):** After a move or resize by PdfCraft's own title bar or edges, the button release given back to egui goes before the pointer leaving the window, so hover no longer sticks at the old place until the pointer moves. Found by CodeRabbit in review of #3970; a unit test covers it. Estimates unchanged.diff --git a/apps/pdfcraft-cli/src/commands.rs b/apps/pdfcraft-cli/src/commands.rs--- a/apps/pdfcraft-cli/src/commands.rs+++ b/apps/pdfcraft-cli/src/commands.rs@@ -4,6 +4,14 @@ use super::options::{ }; use super::*; +/// A session that honours the machine's administrator policy for JavaScript: opening an XFA+/// form or applying an edit runs no document scripts where an administrator turned them off.+fn session() -> pdfcraft_engine::Session {+    let mut s = pdfcraft_engine::Session::new();+    s.set_javascript_lock(pdfcraft_automation::admin_javascript_lock());+    s+}+ pub(crate) fn read(path: &str) -> Result<Arc<Vec<u8>>, String> {     std::fs::read(path).map(Arc::new).map_err(|e| format!("{path}: {e}")) }@@ -21,7 +29,7 @@ pub(crate) fn info(args: &[String]) -> Result<(), CliError> {     // Dynamic XFA forms: say what laying the template out gives (what the app shows).     let xfa_layout = (info.xfa == Some(pdfcraft_render::Xfa::Dynamic))         .then(|| {-            let mut s = pdfcraft_engine::Session::new();+            let mut s = session();             let id = s.open("info.pdf", None, bytes.clone(), password).ok()?;             let d = s.get(id)?;             d.xfa.as_ref().map(|x| {@@ -87,7 +95,7 @@ pub(crate) fn text(args: &[String]) -> Result<(), CliError> {     let render_password = pdfcraft_engine::render_password_for(bytes.clone(), password);     // Printing the text is copying it, so ask what `text_extract` asks (#1645): without the     // permissions password, a document that doesn't allow copying gives no text.-    let mut session = pdfcraft_engine::Session::new();+    let mut session = session();     if let Some(doc) = session.open(path, None, bytes.clone(), password).ok().and_then(|id| session.get(id)) {         // The engine's hint is about editing in the app; on the command line the way in is         // `--password`.@@ -166,7 +174,7 @@ const EDIT_OPTIONS: &str = "--out, --password, --full, --rotate, --delete, --mov /// (a typo like `--rotat`) is an error, not something to skip — skipping it would write an /// unchanged copy and exit 0, which looks like a successful edit (#133). pub(crate) fn edit(args: &[String]) -> Result<(), CliError> {-    use pdfcraft_engine::{Edit, Session};+    use pdfcraft_engine::Edit;     let mut path: Option<&str> = None;     let mut out: Option<&str> = None;     let mut password: Option<&str> = None;@@ -237,7 +245,7 @@ pub(crate) fn edit(args: &[String]) -> Result<(), CliError> {     }     let path = path.ok_or("edit: missing file")?;     let out = out.ok_or("edit: missing --out")?;-    let mut session = Session::new();+    let mut session = session();     let id = session.open(path, Some(path.to_string()), read(path)?, password).map_err(|e| e.to_string())?;     for e in edits {         // A destination past the end must fail like `page_move` / `page_insert_blank` do, not@@ -289,7 +297,7 @@ pub(crate) fn combine(args: &[String]) -> Result<(), CliError> {         return Err("combine: give at least two input files".into());     }     let sources = inputs.iter().map(|p| Ok((file_stem(p), read(p)?))).collect::<Result<Vec<_>, String>>()?;-    let bytes = pdfcraft_engine::Session::new().combine(&sources).map_err(|e| e.to_string())?;+    let bytes = session().combine(&sources).map_err(|e| e.to_string())?;     write_pdf(Path::new(out), &bytes) } @@ -298,7 +306,7 @@ pub(crate) fn extract(args: &[String]) -> Result<(), CliError> {     let path = *positional(args, EXTRACT_OPTIONS).first().ok_or("extract: missing file")?;     let out = flag(args, "--out").ok_or("extract: missing --out")?;     let pages = page_list(flag(args, "--pages").ok_or("extract: missing --pages")?)?;-    let mut session = pdfcraft_engine::Session::new();+    let mut session = session();     let id = session.open(path, None, read(path)?, flag(args, "--password")).map_err(|e| e.to_string())?;     let bytes = session.extract(id, &pages).map_err(|e| e.to_string())?;     write_pdf(Path::new(out), &bytes)@@ -323,7 +331,7 @@ pub(crate) fn split(args: &[String]) -> Result<(), CliError> {         _ => return Err("split: give either --every N or --before PAGES".into()),     };     let dir = PathBuf::from(flag(args, "--out-dir").unwrap_or("."));-    let mut session = pdfcraft_engine::Session::new();+    let mut session = session();     let id = session.open(path, None, read(path)?, flag(args, "--password")).map_err(|e| e.to_string())?;     let stem = file_stem(path);     let parts = session.split(id, &by).map_err(|e| e.to_string())?;@@ -374,7 +382,7 @@ pub(crate) fn render(args: &[String]) -> Result<(), CliError> {     // copy take either.     let render_password = pdfcraft_engine::render_password_for(bytes.clone(), password);     let display = if inspect(bytes.clone(), render_password.as_deref()).is_ok_and(|info| info.xfa == Some(pdfcraft_render::Xfa::Dynamic)) {-        let mut session = pdfcraft_engine::Session::new();+        let mut session = session();         let id = session.open(path, None, bytes, password).map_err(|e| e.to_string())?;         session.get(id).ok_or("render: the document could not be opened")?.display.clone()     } else {diff --git a/apps/pdfcraft-cli/src/run.rs b/apps/pdfcraft-cli/src/run.rs--- a/apps/pdfcraft-cli/src/run.rs+++ b/apps/pdfcraft-cli/src/run.rs@@ -6,7 +6,7 @@ use super::*; // ---- automation --------------------------------------------------------------------------------  pub(crate) fn automation(args: &[String]) -> Result<pdfcraft_automation::Automation, String> {-    let a = pdfcraft_automation::Automation::new();+    let a = pdfcraft_automation::Automation::new().with_javascript_lock(pdfcraft_automation::admin_javascript_lock());     match flag(args, "--root") {         Some(root) => a.with_root(root).map_err(|e| format!("--root {root}: {e}")),         None => Ok(a),diff --git a/apps/pdfcraft/src/main.rs b/apps/pdfcraft/src/main.rs--- a/apps/pdfcraft/src/main.rs+++ b/apps/pdfcraft/src/main.rs@@ -413,6 +413,8 @@ fn app_creator<'a>(         if let Some(json) = cc.storage.and_then(|s| s.get_string("pdfcraft").or_else(|| s.get_string(LEGACY_STORAGE_KEY))) {             app.restore(&json);         }+        // Administrators' policies win over the saved preferences.+        app.apply_admin_policy();         app.window_state = pdfcraft_ui_egui::window_state::WindowState::started_with(cc.storage);         app.integrated_titlebar = integrated;         app.window_controls = window_controls;diff --git a/crates/automation/src/a11y/documents.rs b/crates/automation/src/a11y/documents.rs--- a/crates/automation/src/a11y/documents.rs+++ b/crates/automation/src/a11y/documents.rs@@ -130,6 +130,8 @@ impl Automation {             Action { name: "Custom".into(), description: String::new(), steps, builtin: false }         };         let folder = self.resolve(a.str("folder")?, true)?;+        // Each file runs in a session of its own: it takes this session's JavaScript setting.+        let scripting = self.session.scripting();         std::fs::create_dir_all(&folder).map_err(|e| failed(e.to_string()))?;         let mut out = Vec::new();         for p in a.strs("paths")? {@@ -138,7 +140,7 @@ impl Automation {                 .resolve(p, false)                 .map_err(|e| e.to_string())                 .and_then(|src| crate::read_input_bytes(&src).map_err(|e| e.to_string()))-                .and_then(|bytes| run_on(&action, &name, std::sync::Arc::new(bytes), |_, _| {}));+                .and_then(|bytes| run_on(&action, &name, std::sync::Arc::new(bytes), &scripting, |_, _| {}));             out.push(match result {                 Ok(r) => {                     let target = child(&folder, &name);diff --git a/crates/automation/src/a11y/form_tools.rs b/crates/automation/src/a11y/form_tools.rs--- a/crates/automation/src/a11y/form_tools.rs+++ b/crates/automation/src/a11y/form_tools.rs@@ -156,8 +156,18 @@ impl Automation {      pub(crate) fn js_enabled(&mut self, a: &Args) -> Result<Value> {         if let Some(on) = a.opt_bool("enabled")? {+            if on && let Some(lock) = self.session.javascript_lock() {+                return Err(ToolError::Failed(format!(+                    "JavaScript is turned off by an administrator's policy ({}) and can't be turned on",+                    lock.source+                )));+            }             self.session.set_javascript(on);         }-        Ok(json!({ "enabled": self.session.javascript() }))+        let mut out = json!({ "enabled": self.session.javascript() });+        if let Some(lock) = self.session.javascript_lock() {+            out["policy"] = json!({ "source": lock.source, "unreadable": lock.unreadable });+        }+        Ok(out)     } }diff --git a/crates/automation/src/a11y/ocr.rs b/crates/automation/src/a11y/ocr.rs--- a/crates/automation/src/a11y/ocr.rs+++ b/crates/automation/src/a11y/ocr.rs@@ -45,12 +45,15 @@ impl Automation {         }         std::fs::create_dir_all(&folder).map_err(|e| failed(e.to_string()))?;         let ocr = pdfcraft_engine::ocr::engine().map_err(failed)?;+        let scripting = self.session.scripting();         let mut out = Vec::new();         for p in paths {             let name = input_file_name(p);             let result =                 self.resolve(p, false).map_err(|e| e.to_string()).and_then(|src| crate::read_input_bytes(&src).map_err(|e| e.to_string())).and_then(-                    |bytes| pdfcraft_engine::ocr::recognize_file(&name, std::sync::Arc::new(bytes), None, settings.clone(), &ocr, |_, _| true),+                    |bytes| {+                        pdfcraft_engine::ocr::recognize_file(&name, std::sync::Arc::new(bytes), None, settings.clone(), &ocr, &scripting, |_, _| true)+                    },                 );             out.push(match result {                 Ok(r) => {diff --git a/crates/automation/src/batch.rs b/crates/automation/src/batch.rs--- a/crates/automation/src/batch.rs+++ b/crates/automation/src/batch.rs@@ -18,6 +18,8 @@ struct Recipe<'a> {     steps: Vec<&'a str>,     images: optimize::ImageSettings,     ocr: ocr::OcrSettings,+    /// The calling session's JavaScript setting, for each file's own session.+    scripting: pdfcraft_engine::js::Scripting, }  impl<'a> Recipe<'a> {@@ -54,7 +56,7 @@ impl<'a> Recipe<'a> {         if args.opt_num("dpi")?.is_some_and(|dpi| !(72.0..=600.0).contains(&dpi)) {             return Err(Args::wrong("dpi", "72 to 600"));         }-        Ok(Self { steps, images, ocr: auto.ocr_settings(args)? })+        Ok(Self { steps, images, ocr: auto.ocr_settings(args)?, scripting: auto.session.scripting() })     }      fn accepts(&self, path: &Path) -> bool {@@ -65,6 +67,7 @@ impl<'a> Recipe<'a> {     fn process(&self, path: &Path) -> Result<(Arc<Vec<u8>>, Vec<Value>)> {         // A fresh session per file bounds retained document state and never touches caller tabs.         let mut session = Session::new();+        session.set_scripting(&self.scripting);         let mut input = Vec::new();         std::fs::File::open(path).map_err(failed)?.take(MAX_INPUT + 1).read_to_end(&mut input).map_err(failed)?;         if input.len() as u64 > MAX_INPUT {@@ -79,7 +82,7 @@ impl<'a> Recipe<'a> {                 "convert" => bytes = session.convert_to_pdf(name, &bytes)?.1,                 "ocr" => {                     let engine = ocr::engine().map_err(failed)?;-                    let result = ocr::recognize_file(name, bytes, None, self.ocr.clone(), &engine, |_, _| true).map_err(failed)?;+                    let result = ocr::recognize_file(name, bytes, None, self.ocr.clone(), &engine, &self.scripting, |_, _| true).map_err(failed)?;                     log.push(                         json!({"step":"ocr","words":result.words(),"skipped_pages":result.pages.iter().filter(|p| p.skipped.is_some()).count()}),                     );diff --git a/crates/automation/src/lib.rs b/crates/automation/src/lib.rs--- a/crates/automation/src/lib.rs+++ b/crates/automation/src/lib.rs@@ -90,6 +90,12 @@ pub struct Automation {     evicted: HashSet<DocId>, } +/// The machine's administrator policy for JavaScript (`pdfcraft_platform::policy`), for+/// [`Automation::with_javascript_lock`] and the CLI's own sessions.+pub fn admin_javascript_lock() -> Option<pdfcraft_engine::js::JavaScriptLock> {+    pdfcraft_platform::policy::Policy::load().javascript_lock().map(|(source, unreadable)| pdfcraft_engine::js::JavaScriptLock { source, unreadable })+}+ impl Default for Automation {     fn default() -> Self {         Self::new()@@ -133,6 +139,12 @@ impl Automation {         self     } +    /// Keep JavaScript off as an administrator's policy (`None`: no policy).+    pub fn with_javascript_lock(mut self, lock: Option<pdfcraft_engine::js::JavaScriptLock>) -> Self {+        self.session.set_javascript_lock(lock);+        self+    }+     /// Multiply the time XFA form scripts may take (tests). See [`Session::with_xfa_time_scale`].     pub fn with_xfa_time_scale(mut self, factor: u32) -> Self {         self.session = std::mem::take(&mut self.session).with_xfa_time_scale(factor);diff --git a/crates/automation/src/tools.rs b/crates/automation/src/tools.rs--- a/crates/automation/src/tools.rs+++ b/crates/automation/src/tools.rs@@ -1211,7 +1211,7 @@ pub fn tools() -> Vec<ToolDef> {                     "hide": { "type": "array", "items": { "type": "string" } }, "submit": { "type": "string" } }, "required": ["trigger"] } } }),                 &["doc", "field", "actions"],             )),-        t("js_enabled", "JavaScript on or off", "Preferences ▸ JavaScript ▸ Enable Acrobat JavaScript: set it with `enabled`, or read it. With JavaScript off, field scripts other than Acrobat's AF calls don't run.")+        t("js_enabled", "JavaScript on or off", "Preferences ▸ JavaScript ▸ Enable Acrobat JavaScript: set it with `enabled`, or read it. With JavaScript off, field scripts other than Acrobat's AF calls don't run. Where an administrator's policy (DisableJavaScript) keeps it off, the answer has `policy` (where it is set) and `enabled: true` fails.")             .with(schema(json!({ "enabled": { "type": "boolean" } }), &[])),         t("ocr_recognize", "Recognize text (OCR)", "Scan & OCR ▸ Recognize text: render pages, read the words in them and add them as invisible text over the page image, so scanned pages become searchable and selectable (a searchable image; the image is not changed). Pages that already have text are skipped unless skip_text_pages is false; then pictures on those pages are read too, and recognised words that the page's own text already has (an earlier OCR layer included) are left out and counted as already_on_page. Returns each page's recognised text (left out when the document's security doesn't allow copying), word count or why it was skipped. Needs the OCR models (ocr_status). Undoable as one step.")             .cmd("ocr.recognize")diff --git a/crates/automation/tests/it/automation.rs b/crates/automation/tests/it/automation.rs--- a/crates/automation/tests/it/automation.rs+++ b/crates/automation/tests/it/automation.rs@@ -6502,6 +6502,20 @@ fn javascript_through_tools() {     assert!(a.call("js_run", &json!({ "doc": doc, "script": "1" })).is_err()); } +#[test]+fn an_administrators_policy_keeps_javascript_off() {+    let lock = pdfcraft_engine::js::JavaScriptLock { source: "/etc/pdfcraft/policy.json (DisableJavaScript)".into(), unreadable: None };+    let mut a = Automation::new().with_javascript_lock(Some(lock));+    let r = ok(&mut a, "js_enabled", json!({}));+    assert_eq!(r["enabled"], false);+    assert_eq!(r["policy"]["source"], "/etc/pdfcraft/policy.json (DisableJavaScript)");+    assert!(r["policy"]["unreadable"].is_null());+    let err = a.call("js_enabled", &json!({ "enabled": true })).unwrap_err().to_string();+    assert!(err.contains("administrator's policy") && err.contains("/etc/pdfcraft/policy.json"), "{err}");+    // Turning it off is still the person's choice to make.+    assert_eq!(ok(&mut a, "js_enabled", json!({ "enabled": false }))["enabled"], false);+}+ #[test] fn merging_form_data_into_a_spreadsheet() {     let dir = workdir("merge-data");@@ -6690,6 +6704,28 @@ fn actions_through_tools() {     assert!(a.call("action_run", &json!({ "steps": [{ "step": "fly" }], "paths": ["a.pdf"], "folder": "x" })).is_err()); } +/// `action_run` processes each file in a session of its own, which takes this session's+/// JavaScript setting: a "Run a JavaScript" step runs only where this session's scripts would.+#[test]+fn action_run_follows_the_sessions_javascript_setting() {+    let dir = workdir("action-javascript");+    let args = |out: &str| json!({ "steps": [{ "step": "run_javascript", "arg": "1 + 1" }], "paths": ["a.pdf"], "folder": out });+    let mut a = auto(&dir);+    let r = ok(&mut a, "action_run", args("on"));+    assert_eq!(r["files"][0]["log"][0], "Run a JavaScript", "{r}");++    let lock = pdfcraft_engine::js::JavaScriptLock { source: "policy".into(), unreadable: None };+    let mut a = auto(&dir).with_javascript_lock(Some(lock));+    let r = ok(&mut a, "action_run", args("locked"));+    assert!(r["files"][0]["error"].as_str().is_some_and(|e| e.contains("administrator's policy")), "{r}");+    assert!(!dir.join("locked/a.pdf").exists());++    let mut a = auto(&dir);+    ok(&mut a, "js_enabled", json!({ "enabled": false }));+    let r = ok(&mut a, "action_run", args("off"));+    assert!(r["files"][0]["error"].as_str().is_some_and(|e| e.contains("Preferences")), "{r}");+}+ #[test] fn action_run_keeps_outputs_with_duplicate_input_basenames() {     let dir = workdir("action-duplicate-basenames");diff --git a/crates/automation/tests/it/batch.rs b/crates/automation/tests/it/batch.rs--- a/crates/automation/tests/it/batch.rs+++ b/crates/automation/tests/it/batch.rs@@ -17,6 +17,55 @@ fn pdf() -> Vec<u8> {     PDF.get_or_init(|| Session::new().create_from_text("Original", "Original text").unwrap().as_ref().clone()).clone() } +/// `pdf` with an unreferenced, compressible object added in an incremental update. A full+/// rewrite drops it, so the optimize step's output is smaller and is kept, and it shows what the+/// form's scripts changed on opening (the plain fixture is already as small as it gets).+fn padded(mut pdf: Vec<u8>) -> Vec<u8> {+    let text = String::from_utf8_lossy(&pdf).into_owned();+    let prev: usize = text.rsplit("startxref").next().unwrap().trim().trim_end_matches("%%EOF").trim().parse().unwrap();+    let at = pdf.len();+    let pad = "0".repeat(100_000);+    pdf.extend_from_slice(format!("6 0 obj\n<< /Length {} >>\nstream\n{pad}\nendstream\nendobj\n", pad.len()).as_bytes());+    let xref = pdf.len();+    pdf.extend_from_slice(+        format!("xref\n6 1\n{at:010} 00000 n \ntrailer\n<< /Size 7 /Root 1 0 R /Prev {prev} >>\nstartxref\n{xref}\n%%EOF\n").as_bytes(),+    );+    pdf+}++#[test]+fn batch_files_follow_the_sessions_javascript_setting() {+    // Each file is processed in a session of its own; opening this XFA form there runs its+    // initialize script (qty = 2) only where the calling session would.+    let dir = tempfile::tempdir().unwrap();+    std::fs::write(dir.path().join("form.pdf"), padded(pdfcraft_xfa::fixtures::shell(&pdfcraft_xfa::fixtures::scripted_template()))).unwrap();+    let qty = |out: &str| {+        let bytes = std::fs::read(dir.path().join(out).join("form.pdf")).unwrap();+        let mut s = Session::new();+        s.set_javascript(false);+        let id = s.open("form.pdf", None, std::sync::Arc::new(bytes), None).unwrap();+        s.get(id).unwrap().form.iter().find(|f| f.name == "qty").unwrap().value.clone()+    };+    let run = |auto: &mut Automation, out: &str| {+        let report = result(auto, json!({"paths":["form.pdf"],"steps":["optimize"],"out_dir":out}));+        assert_eq!(report["completed"], 1, "{report}");+    };+    // Control: with no policy the script runs, and its change is in the output.+    let mut auto = Automation::new().with_root(dir.path()).unwrap();+    run(&mut auto, "on");+    assert_eq!(qty("on"), ["2"]);++    let lock = pdfcraft_engine::js::JavaScriptLock { source: "policy".into(), unreadable: None };+    let mut auto = Automation::new().with_root(dir.path()).unwrap().with_javascript_lock(Some(lock));+    run(&mut auto, "locked");+    assert!(qty("locked").iter().all(|v| v.is_empty()), "an administrator's policy: {:?}", qty("locked"));++    let mut auto = Automation::new().with_root(dir.path()).unwrap();+    auto.call("js_enabled", &json!({ "enabled": false })).unwrap();+    run(&mut auto, "off");+    assert!(qty("off").iter().all(|v| v.is_empty()), "the preference: {:?}", qty("off"));+}+ #[test] fn pdf_fixture_remains_identical_across_metadata_clock_tick() {     let before = pdf();diff --git a/crates/engine/src/actions.rs b/crates/engine/src/actions.rs--- a/crates/engine/src/actions.rs+++ b/crates/engine/src/actions.rs@@ -203,9 +203,18 @@ fn apply_step(s: &mut Session, id: DocId, step: &Step, log: &mut Vec<String>) ->     Ok(None) } -/// Run `action` on one file. `progress(step, steps)` is called before each step.-pub fn run_on(action: &Action, name: &str, bytes: Arc<Vec<u8>>, mut progress: impl FnMut(usize, usize)) -> Result<FileResult, String> {+/// Run `action` on one file. `progress(step, steps)` is called before each step. `scripting` is+/// the caller's JavaScript setting ([`Session::scripting`]): the file's scripts, and a "Run a+/// JavaScript" step, run only where the caller's would.+pub fn run_on(+    action: &Action,+    name: &str,+    bytes: Arc<Vec<u8>>,+    scripting: &crate::js::Scripting,+    mut progress: impl FnMut(usize, usize),+) -> Result<FileResult, String> {     let mut s = Session::new();+    s.set_scripting(scripting);     let mut id = s.open(name, None, bytes, None).map_err(|e| e.to_string())?;     if let Some(why) = s.get(id).and_then(|d| d.read_only_reason.clone()) {         return Err(why);diff --git a/crates/engine/src/js.rs b/crates/engine/src/js.rs--- a/crates/engine/src/js.rs+++ b/crates/engine/src/js.rs@@ -4,7 +4,8 @@ //! printing, navigation, links) is kept for the front end in [`JsOutput`]. //! //! Preferences ▸ JavaScript ▸ Enable Acrobat JavaScript is [`Session::set_javascript`]; with it-//! off, scripts don't run and AF calls keep working natively.+//! off, scripts don't run and AF calls keep working natively. An administrator's policy+//! ([`Session::set_javascript_lock`]) keeps it off whatever the preference says.  use pdfcraft_forms::{Field, FieldChange, FieldEvent, FieldKind, FieldProps, ScriptResult, Scripts}; pub use pdfcraft_js::{Limits, Outcome, Panel, Request};@@ -245,16 +246,74 @@ impl crate::Document {     } } +/// An administrator's policy that keeps JavaScript off (the front end reads it from the+/// operating system; see `pdfcraft_platform::policy`).+#[derive(Clone, Debug, PartialEq, Eq)]+pub struct JavaScriptLock {+    /// Where the policy was found, e.g. `HKLM\SOFTWARE\Policies\PdfCraft\DisableJavaScript`.+    pub source: String,+    /// Set when a policy is there but could not be read: JavaScript stays off until an+    /// administrator fixes it, and this says what was wrong.+    pub unreadable: Option<String>,+}++/// Whether scripts may run, as a value: the person's preference and any administrator's lock.+/// Helpers that open files in a session of their own (the Action Wizard's [`crate::actions::run_on`],+/// multi-file OCR's [`crate::ocr::recognize_file`], batch runs) take it from the caller's session+/// with [`Session::scripting`], so a file processed there runs scripts exactly when the caller's+/// would. A fresh `Session::new()` has JavaScript on and no lock.+#[derive(Clone, Debug, Default, PartialEq, Eq)]+pub struct Scripting {+    off: bool,+    lock: Option<JavaScriptLock>,+}++impl Scripting {+    /// Whether scripts run under this setting.+    pub fn javascript(&self) -> bool {+        !self.off && self.lock.is_none()+    }+}+ impl Session {-    /// Preferences ▸ JavaScript ▸ Enable Acrobat JavaScript (on by default).+    /// This session's JavaScript setting, for a helper's own session ([`Scripting`]).+    pub fn scripting(&self) -> Scripting {+        Scripting { off: self.js_off, lock: self.js_lock.clone() }+    }++    /// Take another session's JavaScript setting, preference and lock alike. Meant for a+    /// helper's fresh session: it replaces any lock this session had.+    pub fn set_scripting(&mut self, scripting: &Scripting) {+        self.js_off = scripting.off;+        self.js_lock = scripting.lock.clone();+    }++    /// Preferences ▸ JavaScript ▸ Enable Acrobat JavaScript (on by default). While an+    /// administrator's policy keeps JavaScript off ([`Session::set_javascript_lock`]) the choice+    /// is still remembered, and applies again once the policy is gone.     pub fn set_javascript(&mut self, on: bool) {         self.js_off = !on;     } +    /// Whether scripts run: the user's choice, unless a policy keeps JavaScript off.     pub fn javascript(&self) -> bool {+        !self.js_off && self.js_lock.is_none()+    }++    /// The user's own choice, whatever a policy says (what Preferences saves).+    pub fn javascript_preference(&self) -> bool {         !self.js_off     } +    /// An administrator's policy keeps JavaScript off (`Some`), or doesn't (`None`).+    pub fn set_javascript_lock(&mut self, lock: Option<JavaScriptLock>) {+        self.js_lock = lock;+    }++    pub fn javascript_lock(&self) -> Option<&JavaScriptLock> {+        self.js_lock.as_ref()+    }+     /// What scripts printed or asked for since the last call (alerts, console, print, …).     pub fn take_js_output(&mut self, id: DocId) -> JsOutput {         self.docs.iter_mut().find(|d| d.id == id).map(|d| std::mem::take(&mut d.js_output)).unwrap_or_default()@@ -264,6 +323,9 @@ impl Session {     /// the console (no target). Field changes and `resetForm` are applied as one undoable step;     /// everything else the script asked for is returned.     pub fn run_javascript(&mut self, id: DocId, script: &str, target: Option<&str>) -> Result<Outcome, EditError> {+        if self.js_lock.is_some() {+            return Err(EditError::Invalid("JavaScript is turned off by your administrator's policy".into()));+        }         if self.js_off {             return Err(EditError::Invalid("JavaScript is turned off (Preferences ▸ JavaScript)".into()));         }diff --git a/crates/engine/src/ocr.rs b/crates/engine/src/ocr.rs--- a/crates/engine/src/ocr.rs+++ b/crates/engine/src/ocr.rs@@ -235,16 +235,20 @@ impl FileResult { }  /// Recognize text in multiple files: one PDF's bytes in, the searchable PDF out. `progress`-/// works as for [`OcrJob::run`].+/// works as for [`OcrJob::run`]. `scripting` is the caller's JavaScript setting+/// ([`Session::scripting`]), so opening an XFA form here runs its scripts only where the+/// caller's session would. pub fn recognize_file(     name: &str,     bytes: Arc<Vec<u8>>,     password: Option<&str>,     settings: OcrSettings,     ocr: &Ocr,+    scripting: &crate::js::Scripting,     progress: impl FnMut(usize, usize) -> bool, ) -> Result<FileResult, String> {     let mut s = Session::new();+    s.set_scripting(scripting);     let id = s.open(name, None, bytes.clone(), password).map_err(|e| e.to_string())?;     if let Some(why) = s.get(id).and_then(|d| d.read_only_reason.clone()) {         return Err(why);diff --git a/crates/engine/src/open.rs b/crates/engine/src/open.rs--- a/crates/engine/src/open.rs+++ b/crates/engine/src/open.rs@@ -43,7 +43,8 @@ impl OpenContext { impl Session {     /// Snapshot the session state that affects open-time scripts, dates and signature trust.     pub fn open_context(&self) -> OpenContext {-        OpenContext { now: self.now(), js_off: self.js_off, trust: self.trust.clone(), xfa_time_scale: self.xfa_time_scale.unwrap_or(1) }+        // The effective setting: an administrator's policy holds on the background open path too.+        OpenContext { now: self.now(), js_off: !self.javascript(), trust: self.trust.clone(), xfa_time_scale: self.xfa_time_scale.unwrap_or(1) }     }      /// Prepare a document without adding it to this session.diff --git a/crates/engine/src/session.rs b/crates/engine/src/session.rs--- a/crates/engine/src/session.rs+++ b/crates/engine/src/session.rs@@ -38,6 +38,9 @@ pub struct Session {     pub(crate) trust: Arc<TrustStore>,     /// Preferences ▸ JavaScript ▸ Enable Acrobat JavaScript, inverted (on by default).     pub(crate) js_off: bool,+    /// An administrator's policy that keeps JavaScript off, whatever `js_off` says. Read+    /// [`Session::javascript`], never `js_off` alone, to decide whether scripts run.+    pub(crate) js_lock: Option<js::JavaScriptLock>,     /// Preferences ▸ Date format, when not the default (see [`dates`]).     pub(crate) date_format: Option<String>,     /// Preferences ▸ Date format ▸ Language, when not following the interface language.diff --git a/crates/engine/src/session/editing.rs b/crates/engine/src/session/editing.rs--- a/crates/engine/src/session/editing.rs+++ b/crates/engine/src/session/editing.rs@@ -5,7 +5,7 @@ impl Session {     pub fn apply(&mut self, id: DocId, edit: Edit) -> Result<(), EditError> {         let now = self.now();         let today = self.today();-        let js_off = self.js_off;+        let js_off = !self.javascript();         let xfa_time_scale = self.xfa_time_scale.unwrap_or(1);         let doc = self.doc_mut(id)?;         doc.edit_notices.clear();diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs--- a/crates/engine/src/tests.rs+++ b/crates/engine/src/tests.rs@@ -5234,7 +5234,7 @@ fn actions_run_their_steps_on_files() {     let src = s.create_from_text("t", "Quarterly numbers").unwrap();     let built = actions::builtin();     let numbers = built.iter().find(|a| a.name == "Add Page Numbers").unwrap();-    let r = actions::run_on(numbers, "q.pdf", src.clone(), |_, _| {}).unwrap();+    let r = actions::run_on(numbers, "q.pdf", src.clone(), &js::Scripting::default(), |_, _| {}).unwrap();     let mut s2 = Session::new();     let id = s2.open("out.pdf", None, r.bytes, None).unwrap();     assert!(page_texts(&s2, id)[0].contains("Page 1 of 1"), "{:?}", page_texts(&s2, id));@@ -5252,14 +5252,14 @@ fn actions_run_their_steps_on_files() {         builtin: false,     };     let mut steps = Vec::new();-    let r = actions::run_on(&custom, "q.pdf", src, |i, n| steps.push((i, n))).unwrap();+    let r = actions::run_on(&custom, "q.pdf", src, &js::Scripting::default(), |i, n| steps.push((i, n))).unwrap();     assert_eq!(steps, [(0, 4), (1, 4), (2, 4), (3, 4)]);     assert_eq!(r.log.len(), 4, "{:?}", r.log);     let id = s2.open("out2.pdf", None, r.bytes, None).unwrap();     let d = s2.get(id).unwrap();     assert_eq!(d.info.title.as_deref(), Some("Q3"));     assert!(page_texts(&s2, id)[0].contains("DRAFT"));-    assert!(actions::run_on(&custom, "bad.pdf", Arc::new(b"nope".to_vec()), |_, _| {}).is_err());+    assert!(actions::run_on(&custom, "bad.pdf", Arc::new(b"nope".to_vec()), &js::Scripting::default(), |_, _| {}).is_err()); }  /// A removal step that fails (here: a signed file can't be rewritten without its hidden data)@@ -5276,7 +5276,7 @@ fn actions_fail_when_hidden_information_could_not_be_removed() {     let signed = s.sign(id, &digital_id, SignOptions { page: 1, ..SignOptions::default() }).unwrap();     let built = actions::builtin();     let sanitize = built.iter().find(|a| a.name == "Sanitize Documents").unwrap();-    let e = actions::run_on(sanitize, "signed.pdf", signed, |_, _| {}).map(|r| r.log).unwrap_err();+    let e = actions::run_on(sanitize, "signed.pdf", signed, &js::Scripting::default(), |_, _| {}).map(|r| r.log).unwrap_err();     assert!(e.starts_with("Sanitize document:"), "{e}"); } @@ -6614,6 +6614,103 @@ fn xfa_scripts_stay_off_with_javascript_off() {     assert!(!s.get(id).unwrap().xfa_scripts_off(), "the preference, not a script that ran away"); } +fn admin_lock() -> Option<js::JavaScriptLock> {+    Some(js::JavaScriptLock { source: "HKLM\\SOFTWARE\\Policies\\PdfCraft\\DisableJavaScript".into(), unreadable: None })+}++#[test]+fn an_administrators_lock_keeps_scripts_off_whatever_the_preference() {+    let mut s = Session::new().with_clock(|| 1_700_000_000);+    s.set_javascript_lock(admin_lock());+    assert!(!s.javascript());+    assert!(s.javascript_preference(), "the user's own choice is untouched");+    // Turning it on in Preferences doesn't get past the policy.+    s.set_javascript(true);+    assert!(!s.javascript());+    let id = s.open("order.pdf", None, Arc::new(scripted_form()), None).unwrap();+    let value = |s: &Session, n: &str| s.get(id).unwrap().form.iter().find(|f| f.name == n).unwrap().value.clone();+    s.apply(id, Edit::SetFieldValue { name: "qty".into(), value: FieldValue::Text("4".into()) }).unwrap();+    assert!(value(&s, "total").iter().all(|v| v.is_empty()), "no calculate script ran: {:?}", value(&s, "total"));+    let err = s.run_javascript(id, "getField('qty').value = 9", Some("go")).unwrap_err();+    assert!(err.to_string().contains("administrator"), "{err}");+    assert_eq!(value(&s, "qty"), ["4"]);+    assert!(s.take_js_output(id).is_empty());++    // Without the policy, the remembered choice applies again.+    s.set_javascript_lock(None);+    assert!(s.javascript());+    s.set_javascript(false);+    s.set_javascript_lock(admin_lock());+    s.set_javascript_lock(None);+    assert!(!s.javascript(), "a policy coming and going doesn't change the preference");+}++#[test]+fn an_administrators_lock_keeps_xfa_scripts_off() {+    let bytes = Arc::new(pdfcraft_xfa::fixtures::shell(&pdfcraft_xfa::fixtures::scripted_template()));+    let mut s = Session::new().with_clock(|| 1_700_000_000);+    s.set_javascript_lock(Some(js::JavaScriptLock { source: "policy.json".into(), unreadable: Some("not JSON".into()) }));+    let id = s.open("scripted.pdf", None, bytes, None).expect("opens");+    let qty = s.get(id).unwrap().form.iter().find(|f| f.name == "qty").unwrap().value.clone();+    assert!(qty.is_empty(), "no initialize script ran");+    assert!(s.run_javascript(id, "", Some("addRow")).is_err());+}++#[test]+fn the_action_wizard_follows_the_callers_javascript_setting() {+    // `run_on` opens each file in a session of its own; the caller's setting must reach it.+    let action = actions::Action {+        name: "Mine".into(),+        description: String::new(),+        steps: vec![actions::Step::RunJavaScript("getField('qty').value = 9".into())],+        builtin: false,+    };+    let src = Arc::new(scripted_form());+    let qty = |bytes: Arc<Vec<u8>>| {+        let mut s = Session::new();+        s.set_javascript(false);+        let id = s.open("out.pdf", None, bytes, None).unwrap();+        s.get(id).unwrap().form.iter().find(|f| f.name == "qty").unwrap().value.clone()+    };+    // Control: with JavaScript on, the step runs and the file comes out with qty 9.+    let on = actions::run_on(&action, "q.pdf", src.clone(), &js::Scripting::default(), |_, _| {}).unwrap();+    assert_eq!(qty(on.bytes), ["9"], "{:?}", on.log);++    let mut locked = Session::new();+    locked.set_javascript_lock(admin_lock());+    let mut off = Session::new();+    off.set_javascript(false);+    for (who, caller) in [("an administrator's policy", locked), ("the preference", off)] {+        let err = actions::run_on(&action, "q.pdf", src.clone(), &caller.scripting(), |_, _| {}).unwrap_err();+        assert!(err.starts_with("Run a JavaScript: JavaScript is turned off"), "{who}: {err}");+    }+}++#[test]+fn an_administrators_lock_holds_on_the_background_open_path() {+    // The app prepares documents on a worker from an `OpenContext` snapshot (#1493). The+    // snapshot must carry the effective setting, so a policy holds even while the person's own+    // preference says JavaScript is on.+    let bytes = Arc::new(pdfcraft_xfa::fixtures::shell(&pdfcraft_xfa::fixtures::scripted_template()));+    let mut s = Session::new().with_clock(|| 1_700_000_000);+    let open_on_a_worker = |s: &mut Session| {+        let context = s.open_context();+        let bytes = Arc::clone(&bytes);+        let prepared = std::thread::spawn(move || context.prepare_open("scripted.pdf", None, bytes, None)).join().unwrap().expect("prepares");+        s.commit_open(prepared)+    };+    let qty = |s: &Session, id| s.get(id).unwrap().form.iter().find(|f| f.name == "qty").unwrap().value.clone();+    // Control: with no policy the form's initialize script fills in qty on open.+    let id = open_on_a_worker(&mut s);+    assert_eq!(qty(&s, id), ["2"], "the initialize script runs without a policy");++    s.set_javascript_lock(admin_lock());+    assert!(s.javascript_preference(), "the person's own choice is still on");+    let id = open_on_a_worker(&mut s);+    assert!(qty(&s, id).iter().all(|v| v.is_empty()), "no initialize script ran under the policy: {:?}", qty(&s, id));+    assert!(s.run_javascript(id, "", Some("addRow")).is_err());+}+ #[test] fn hostile_xfa_scripts_cannot_hang_or_bloat_the_document() {     // A calculate script that adds a row every time it runs, a click script that loops ondiff --git a/crates/platform/Cargo.toml b/crates/platform/Cargo.toml--- a/crates/platform/Cargo.toml+++ b/crates/platform/Cargo.toml@@ -9,8 +9,18 @@ license.workspace = true workspace = true  [dependencies]+# Administrators' policy file on Linux and the BSDs (policy.rs).+serde_json = { workspace = true }++[target.'cfg(windows)'.dependencies]+# Administrators' policies in HKLM and HKCU\SOFTWARE\Policies (policy.rs): safe wrappers over+# the registry API. Already in the tree with this feature through the desktop app.+winsafe = { version = "0.0.29", default-features = false, features = ["advapi"] }  [target.'cfg(target_os = "macos")'.dependencies]+# Administrators' policies as managed preferences (policy.rs): CFPreferences' safe bindings.+# Already in the tree through winit and wgpu.+objc2-core-foundation = { version = "0.3", default-features = false, features = ["std", "CFBase", "CFNumber", "CFPreferences", "CFPropertyList", "CFString"] } # `write_atomically`: a volume that refuses F_FULLFSYNC gets a plain fsync. rustix = { version = "1", features = ["fs"] } diff --git a/crates/platform/src/lib.rs b/crates/platform/src/lib.rs--- a/crates/platform/src/lib.rs+++ b/crates/platform/src/lib.rs@@ -4,4 +4,5 @@ #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]  pub mod desktop_theme;+pub mod policy; pub mod staging;diff --git a/crates/platform/src/policy.rs b/crates/platform/src/policy.rsnew file mode 100644--- /dev/null+++ b/crates/platform/src/policy.rs@@ -0,0 +1,365 @@+//! Administrators' policies: settings an organisation sets for everyone on a machine, which+//! people can't change in Preferences. They are read once, at launch, from where each system's+//! management tools write them:+//!+//! | System | Where |+//! |---|---|+//! | Windows | `HKLM\SOFTWARE\Policies\PdfCraft`, then `HKCU\SOFTWARE\Policies\PdfCraft` (Group Policy, Intune, an RMM's `reg add`); flags are `REG_DWORD` 0 or 1 |+//! | macOS | managed preferences for `ai.storyteller.pdfcraft` (a configuration profile from an MDM); flags are booleans |+//! | Linux, BSD | `/etc/pdfcraft/policy.json` (`/usr/local/etc/pdfcraft/policy.json` on FreeBSD), a JSON object such as `{"DisableJavaScript": true}` |+//!+//! Only administrators can write any of these (`HKCU\SOFTWARE\Policies` included), and a value a+//! user sets in their own preferences domain on macOS isn't "forced", so it's ignored.+//!+//! With no policy, nothing changes. A policy that is there but can't be read (the wrong type,+//! an unknown value, a damaged file, no permission to read it) counts as the most restrictive+//! setting, and says why: an administrator who put something there meant to manage the+//! setting, and a policy that silently fell back to the default would leave a feature on+//! where they believe it is off, with nothing to show it.++/// Turns JavaScript in documents off, and keeps it off (Acrobat's `bDisableJavaScript`).+pub const DISABLE_JAVASCRIPT: &str = "DisableJavaScript";++/// The macOS preferences domain (the app's bundle identifier).+pub const MACOS_DOMAIN: &str = "ai.storyteller.pdfcraft";++/// The Windows policy key, under both `HKLM` and `HKCU`.+pub const WINDOWS_KEY: &str = r"SOFTWARE\Policies\PdfCraft";++/// The policy file on Linux and the BSDs.+pub const POLICY_FILE: &str = if cfg!(target_os = "freebsd") { "/usr/local/etc/pdfcraft/policy.json" } else { "/etc/pdfcraft/policy.json" };++/// A policy file larger than this is refused (it is a handful of flags).+pub const MAX_POLICY_FILE_BYTES: u64 = 64 * 1024;++/// What an administrator decided for one setting.+#[derive(Clone, Debug, Default, PartialEq, Eq)]+pub enum Managed<T> {+    /// No policy: the person using the app decides.+    #[default]+    NotSet,+    /// Set by the policy at `source` (e.g. `HKLM\SOFTWARE\Policies\PdfCraft\DisableJavaScript`).+    Set { value: T, source: String },+    /// A policy is at `source` but couldn't be read; the setting is treated as its most+    /// restrictive value.+    Unreadable { source: String, reason: String },+}++/// Every policy PdfCraft knows.+#[derive(Clone, Debug, Default, PartialEq, Eq)]+pub struct Policy {+    /// [`DISABLE_JAVASCRIPT`].+    pub disable_javascript: Managed<bool>,+}++impl Policy {+    /// The machine's policies. Never fails: what can't be read is [`Managed::Unreadable`].+    pub fn load() -> Policy {+        #[cfg(windows)]+        {+            windows::load()+        }+        #[cfg(target_os = "macos")]+        {+            macos::load()+        }+        #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly"))]+        {+            from_file(std::path::Path::new(POLICY_FILE))+        }+        #[cfg(not(any(+            windows,+            target_os = "macos",+            target_os = "linux",+            target_os = "freebsd",+            target_os = "openbsd",+            target_os = "netbsd",+            target_os = "dragonfly"+        )))]+        {+            Policy::default()+        }+    }++    /// When a policy keeps JavaScript off: where it is, and what was wrong with it if it+    /// couldn't be read.+    pub fn javascript_lock(&self) -> Option<(String, Option<String>)> {+        match &self.disable_javascript {+            Managed::Set { value: true, source } => Some((source.clone(), None)),+            Managed::Unreadable { source, reason } => Some((source.clone(), Some(reason.clone()))),+            Managed::Set { value: false, .. } | Managed::NotSet => None,+        }+    }+}++/// A value as a policy store holds it, before it is checked.+#[derive(Clone, Debug, PartialEq, Eq)]+pub enum Raw {+    Missing,+    Bool(bool),+    Integer(i64),+    /// Anything else, described for the message ("REG_SZ", "a string", …).+    Other(String),+    /// The store couldn't be read.+    Error(String),+}++/// An on/off policy: a boolean, or the number 0 or 1.+pub fn flag(raw: Raw, source: String) -> Managed<bool> {+    match raw {+        Raw::Missing => Managed::NotSet,+        Raw::Bool(value) => Managed::Set { value, source },+        Raw::Integer(0) => Managed::Set { value: false, source },+        Raw::Integer(1) => Managed::Set { value: true, source },+        Raw::Integer(n) => Managed::Unreadable { source, reason: format!("expected 0 or 1, found {n}") },+        Raw::Other(what) => Managed::Unreadable { source, reason: format!("expected 0 or 1, found {what}") },+        Raw::Error(reason) => Managed::Unreadable { source, reason },+    }+}++/// Two policies for the same "disable" flag (machine and user): the one that restricts more+/// wins, and the first one on a tie.+pub fn stricter(a: Managed<bool>, b: Managed<bool>) -> Managed<bool> {+    fn rank(m: &Managed<bool>) -> u8 {+        match m {+            Managed::NotSet => 0,+            Managed::Set { value: false, .. } => 1,+            Managed::Set { value: true, .. } => 2,+            Managed::Unreadable { .. } => 3,+        }+    }+    if rank(&b) > rank(&a) { b } else { a }+}++/// The policies in a policy file's text. Unknown keys are ignored, so a newer policy file+/// works with an older PdfCraft; a file that isn't a JSON object makes every policy+/// unreadable, since there is no telling what it meant to set.+pub fn from_json(text: &[u8], source: &str) -> Policy {+    let source_of = |key: &str| format!("{source} ({key})");+    // Editors on Windows may start the file with a byte-order mark.+    let text = text.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(text);+    let object = match serde_json::from_slice::<serde_json::Value>(text) {+        Ok(serde_json::Value::Object(o)) => o,+        Ok(_) => return unreadable(source_of, "it isn't a JSON object"),+        Err(e) => return unreadable(source_of, &format!("it isn't valid JSON ({e})")),+    };+    let raw = |key: &str| match object.get(key) {+        None => Raw::Missing,+        Some(serde_json::Value::Bool(b)) => Raw::Bool(*b),+        Some(serde_json::Value::Number(n)) => n.as_i64().map(Raw::Integer).unwrap_or_else(|| Raw::Other(format!("the number {n}"))),+        Some(serde_json::Value::String(_)) => Raw::Other("a string".into()),+        Some(serde_json::Value::Null) => Raw::Other("null".into()),+        Some(serde_json::Value::Array(_)) => Raw::Other("a list".into()),+        Some(serde_json::Value::Object(_)) => Raw::Other("an object".into()),+    };+    Policy { disable_javascript: flag(raw(DISABLE_JAVASCRIPT), source_of(DISABLE_JAVASCRIPT)) }+}++/// Every policy unreadable, for the same reason.+fn unreadable(source_of: impl Fn(&str) -> String, reason: &str) -> Policy {+    Policy { disable_javascript: Managed::Unreadable { source: source_of(DISABLE_JAVASCRIPT), reason: reason.to_string() } }+}++/// The policies in the file at `path`; no file means no policies.+pub fn from_file(path: &std::path::Path) -> Policy {+    use std::io::Read;+    let source = path.display().to_string();+    let source_of = |key: &str| format!("{source} ({key})");+    let file = match std::fs::File::open(path) {+        Ok(f) => f,+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Policy::default(),+        Err(e) => return unreadable(source_of, &format!("it couldn't be opened ({e})")),+    };+    let mut text = Vec::new();+    // One byte past the cap tells a file at the cap from one over it.+    match file.take(MAX_POLICY_FILE_BYTES.saturating_add(1)).read_to_end(&mut text) {+        Ok(n) if u64::try_from(n).unwrap_or(u64::MAX) > MAX_POLICY_FILE_BYTES => {+            unreadable(source_of, &format!("it is larger than {} KB", MAX_POLICY_FILE_BYTES / 1024))+        }+        Ok(_) => from_json(&text, &source),+        Err(e) => unreadable(source_of, &format!("it couldn't be read ({e})")),+    }+}++#[cfg(windows)]+mod windows {+    use winsafe::{HKEY, RegistryValue, co};++    use super::{DISABLE_JAVASCRIPT, Policy, Raw, WINDOWS_KEY, flag, stricter};++    pub(super) fn load() -> Policy {+        let machine = flag(value(&HKEY::LOCAL_MACHINE, WINDOWS_KEY, DISABLE_JAVASCRIPT), source("HKLM", DISABLE_JAVASCRIPT));+        let user = flag(value(&HKEY::CURRENT_USER, WINDOWS_KEY, DISABLE_JAVASCRIPT), source("HKCU", DISABLE_JAVASCRIPT));+        Policy { disable_javascript: stricter(machine, user) }+    }++    fn source(root: &str, name: &str) -> String {+        format!(r"{root}\{WINDOWS_KEY}\{name}")+    }++    /// Value `name` of `root\key`; a missing key or value is [`Raw::Missing`].+    pub(super) fn value(root: &HKEY, key: &str, name: &str) -> Raw {+        match root.RegGetValue(Some(key), Some(name), co::RRF::RT_ANY) {+            Ok(RegistryValue::Dword(n)) => Raw::Integer(i64::from(n)),+            Ok(RegistryValue::Qword(n)) => i64::try_from(n).map(Raw::Integer).unwrap_or_else(|_| Raw::Other(format!("the REG_QWORD {n}"))),+            Ok(RegistryValue::Sz(s) | RegistryValue::ExpandSz(s)) => {+                Raw::Other(format!("the REG_SZ \"{}\" (use a REG_DWORD)", s.chars().take(40).collect::<String>()))+            }+            Ok(RegistryValue::MultiSz(_)) => Raw::Other("a REG_MULTI_SZ (use a REG_DWORD)".into()),+            Ok(RegistryValue::Binary(_)) => Raw::Other("a REG_BINARY (use a REG_DWORD)".into()),+            Ok(RegistryValue::None) => Raw::Other("a value with no type (use a REG_DWORD)".into()),+            Err(e) if e == co::ERROR::FILE_NOT_FOUND || e == co::ERROR::PATH_NOT_FOUND => Raw::Missing,+            Err(e) => Raw::Error(format!("it couldn't be read ({e})")),+        }+    }+}++#[cfg(target_os = "macos")]+mod macos {+    use objc2_core_foundation::{CFBoolean, CFNumber, CFPreferencesAppValueIsForced, CFPreferencesCopyAppValue, CFString};++    use super::{DISABLE_JAVASCRIPT, MACOS_DOMAIN, Policy, Raw, flag};++    pub(super) fn load() -> Policy {+        let source = format!("managed preferences for {MACOS_DOMAIN} ({DISABLE_JAVASCRIPT})");+        Policy { disable_javascript: flag(value(DISABLE_JAVASCRIPT), source) }+    }++    /// A key's value, but only when an administrator forced it (a configuration profile):+    /// what a person writes with `defaults write` isn't a policy.+    fn value(key: &'static str) -> Raw {+        let domain = CFString::from_static_str(MACOS_DOMAIN);+        let key = CFString::from_static_str(key);+        if !CFPreferencesAppValueIsForced(&key, &domain) {+            return Raw::Missing;+        }+        let Some(v) = CFPreferencesCopyAppValue(&key, &domain) else { return Raw::Missing };+        if let Some(b) = v.downcast_ref::<CFBoolean>() {+            Raw::Bool(b.as_bool())+        } else if let Some(n) = v.downcast_ref::<CFNumber>() {+            n.as_i64().map(Raw::Integer).unwrap_or_else(|| Raw::Other("a number that isn't 0 or 1".into()))+        } else {+            Raw::Other("a value that isn't a boolean or a number".into())+        }+    }+}++#[cfg(test)]+mod tests {+    use super::*;++    fn source() -> String {+        "policy (DisableJavaScript)".into()+    }++    #[test]+    fn a_flag_is_a_boolean_or_zero_or_one() {+        assert_eq!(flag(Raw::Missing, source()), Managed::NotSet);+        assert_eq!(flag(Raw::Bool(true), source()), Managed::Set { value: true, source: source() });+        assert_eq!(flag(Raw::Bool(false), source()), Managed::Set { value: false, source: source() });+        assert_eq!(flag(Raw::Integer(1), source()), Managed::Set { value: true, source: source() });+        assert_eq!(flag(Raw::Integer(0), source()), Managed::Set { value: false, source: source() });+    }++    #[test]+    fn anything_else_is_unreadable_and_says_why() {+        for (raw, why) in [+            (Raw::Integer(2), "expected 0 or 1, found 2"),+            (Raw::Integer(-1), "expected 0 or 1, found -1"),+            (Raw::Other("a string".into()), "expected 0 or 1, found a string"),+            (Raw::Error("access denied".into()), "access denied"),+        ] {+            assert_eq!(flag(raw, source()), Managed::Unreadable { source: source(), reason: why.into() });+        }+    }++    #[test]+    fn javascript_stays_off_when_the_policy_says_so_or_cant_be_read() {+        let policy = |m| Policy { disable_javascript: m };+        assert_eq!(policy(Managed::NotSet).javascript_lock(), None, "no policy: the user decides");+        assert_eq!(policy(Managed::Set { value: false, source: source() }).javascript_lock(), None);+        assert_eq!(policy(Managed::Set { value: true, source: source() }).javascript_lock(), Some((source(), None)));+        assert_eq!(policy(Managed::Unreadable { source: source(), reason: "bad".into() }).javascript_lock(), Some((source(), Some("bad".into()))));+    }++    #[test]+    fn the_stricter_of_two_policies_wins() {+        let on = |s: &str| Managed::Set { value: true, source: s.into() };+        let off = |s: &str| Managed::Set { value: false, source: s.into() };+        let bad = |s: &str| Managed::Unreadable { source: s.into(), reason: "bad".into() };+        assert_eq!(stricter(Managed::NotSet, Managed::NotSet), Managed::NotSet);+        assert_eq!(stricter(Managed::NotSet, on("user")), on("user"));+        assert_eq!(stricter(off("machine"), on("user")), on("user"), "the machine can't switch on what the user's policy turns off");+        assert_eq!(stricter(on("machine"), off("user")), on("machine"));+        assert_eq!(stricter(on("machine"), on("user")), on("machine"), "a tie names the machine policy");+        assert_eq!(stricter(on("machine"), bad("user")), bad("user"));+        assert_eq!(stricter(off("machine"), Managed::NotSet), off("machine"));+    }++    #[test]+    fn a_policy_file_sets_flags_by_name() {+        let js = |text: &str| from_json(text.as_bytes(), "/etc/pdfcraft/policy.json").disable_javascript;+        let src = "/etc/pdfcraft/policy.json (DisableJavaScript)".to_string();+        assert_eq!(js(r#"{"DisableJavaScript": true}"#), Managed::Set { value: true, source: src.clone() });+        assert_eq!(js(r#"{"DisableJavaScript": 1}"#), Managed::Set { value: true, source: src.clone() });+        assert_eq!(js(r#"{"DisableJavaScript": false}"#), Managed::Set { value: false, source: src.clone() });+        assert_eq!(js(r#"{"DisableJavaScript": 0}"#), Managed::Set { value: false, source: src.clone() });+        assert_eq!(js(r#"{}"#), Managed::NotSet);+        assert_eq!(js(r#"{"SomeLaterPolicy": "x"}"#), Managed::NotSet, "unknown keys are ignored");+        assert_eq!(+            js(r#"{"DisableJavaScript": "yes"}"#),+            Managed::Unreadable { source: src.clone(), reason: "expected 0 or 1, found a string".into() }+        );+        assert_eq!(js(r#"{"DisableJavaScript": 1.5}"#), Managed::Unreadable { source: src, reason: "expected 0 or 1, found the number 1.5".into() });+    }++    #[test]+    fn a_damaged_policy_file_keeps_everything_locked() {+        for text in ["", "{\"DisableJavaScript\": tru", "[true]", "true", "{} {}"] {+            assert!(matches!(from_json(text.as_bytes(), "p").disable_javascript, Managed::Unreadable { .. }), "{text:?} should be unreadable");+        }+        // A byte-order mark is fine.+        let bom = "\u{feff}{\"DisableJavaScript\": 1}";+        assert_eq!(from_json(bom.as_bytes(), "p").javascript_lock(), Some(("p (DisableJavaScript)".into(), None)));+        // Not UTF-8 at all.+        assert!(matches!(from_json(&[0xff, 0xfe, 0x00], "p").disable_javascript, Managed::Unreadable { .. }));+    }++    #[test]+    fn policy_files_on_disk() {+        let dir = std::env::temp_dir().join(format!("pdfcraft-policy-{}", std::process::id()));+        std::fs::create_dir_all(&dir).unwrap();+        assert_eq!(from_file(&dir.join("missing.json")), Policy::default(), "no file, no policy");++        let file = dir.join("policy.json");+        std::fs::write(&file, br#"{"DisableJavaScript": true}"#).unwrap();+        assert_eq!(from_file(&file).javascript_lock().map(|(s, why)| (s.ends_with("(DisableJavaScript)"), why)), Some((true, None)));++        let cap = usize::try_from(MAX_POLICY_FILE_BYTES).unwrap();+        let mut at_cap = br#"{"DisableJavaScript": false}"#.to_vec();+        at_cap.resize(cap, b' ');+        std::fs::write(&file, &at_cap).unwrap();+        assert_eq!(from_file(&file).javascript_lock(), None, "a file at the cap is read");+        at_cap.push(b' ');+        std::fs::write(&file, &at_cap).unwrap();+        let (_, why) = from_file(&file).javascript_lock().unwrap();+        assert!(why.unwrap().contains("larger than 64 KB"));++        // A folder where the file should be can't be read: locked, with the reason.+        let folder = dir.join("folder.json");+        std::fs::create_dir_all(&folder).unwrap();+        assert!(from_file(&folder).javascript_lock().unwrap().1.is_some());+        std::fs::remove_dir_all(&dir).unwrap();+    }++    #[cfg(windows)]+    #[test]+    fn a_missing_registry_key_is_no_policy() {+        // Read-only: a key nobody has, under both roots.+        let key = format!(r"SOFTWARE\Policies\PdfCraft-test-{}-absent", std::process::id());+        assert_eq!(windows::value(&winsafe::HKEY::LOCAL_MACHINE, &key, DISABLE_JAVASCRIPT), Raw::Missing);+        assert_eq!(windows::value(&winsafe::HKEY::CURRENT_USER, &key, DISABLE_JAVASCRIPT), Raw::Missing);+        // An existing key without the value.+        assert_eq!(windows::value(&winsafe::HKEY::CURRENT_USER, "Software", "PdfCraft-test-absent-value"), Raw::Missing);+    }+}diff --git a/crates/ui-egui/src/app/prefs.rs b/crates/ui-egui/src/app/prefs.rs--- a/crates/ui-egui/src/app/prefs.rs+++ b/crates/ui-egui/src/app/prefs.rs@@ -129,7 +129,8 @@ impl PdfCraftApp {             "digital_ids": self.digital_ids.iter().filter(|e| !e.path.starts_with("keychain:") && !e.path.starts_with("windows:")).collect::<Vec<_>>(),             "trusted": trusted,             "custom_stamps": stamps_ui::encode(&self.custom_stamps),-            "javascript": self.session.javascript(),+            // The person's own choice, not what an administrator's policy forces.+            "javascript": self.session.javascript_preference(),             "actions": actions_ui::encode(&self.custom_actions),             "combine_columns": self.combine_columns.to_json(),             "combine_view": self.combine_view.as_str(),diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv--- a/crates/ui-egui/src/i18n/ar.tsv+++ b/crates/ui-egui/src/i18n/ar.tsv@@ -1286,6 +1286,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	طُبِّقت {n} علامات حجب. احفظ لإزالة المحتوى من الملف. 	JavaScript Console	وحدة تحكم JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript متوقف (التفضيلات > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript متوقف من قِبل المسؤول.+	Turned off by your administrator.	أوقفه المسؤول.+	Turned off because your administrator's policy couldn't be read.	متوقف لأنه تعذّرت قراءة سياسة المسؤول. 	Output appears here.	يظهر الناتج هنا. 	Script Name:	اسم السكربت: 	This document has no document-level scripts.	هذا المستند لا يحتوي على سكربتات على مستوى المستند.diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv--- a/crates/ui-egui/src/i18n/bg.tsv+++ b/crates/ui-egui/src/i18n/bg.tsv@@ -955,6 +955,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	Приложени са {n} маркировки за заличаване. Запишете, за да премахнете съдържанието от файла. 	JavaScript Console	Конзола за JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript е изключен (Настройки > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript е изключен от вашия администратор.+	Turned off by your administrator.	Изключено от вашия администратор.+	Turned off because your administrator's policy couldn't be read.	Изключено, защото правилата на вашия администратор не могат да бъдат прочетени. 	Output appears here.	Резултатът се показва тук. 	Document JavaScripts	JavaScript скриптове на документа 	Script Name:	Име на скрипта:diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv--- a/crates/ui-egui/src/i18n/ca.tsv+++ b/crates/ui-egui/src/i18n/ca.tsv@@ -1238,6 +1238,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	S'han aplicat {n} marques de censura. Desa per eliminar el contingut del fitxer. 	JavaScript Console	Consola de JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript està desactivat (Preferències > JavaScript).+	JavaScript is turned off by your administrator.	L'administrador ha desactivat JavaScript.+	Turned off by your administrator.	Desactivat per l'administrador.+	Turned off because your administrator's policy couldn't be read.	Desactivat perquè no s'ha pogut llegir la política de l'administrador. 	Output appears here.	La sortida apareix aquí. 	Script Name:	Nom de l'script: 	This document has no document-level scripts.	Aquest document no té scripts a nivell de document.diff --git a/crates/ui-egui/src/i18n/ckb.tsv b/crates/ui-egui/src/i18n/ckb.tsv--- a/crates/ui-egui/src/i18n/ckb.tsv+++ b/crates/ui-egui/src/i18n/ckb.tsv@@ -1254,6 +1254,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} نیشانەی شاردنەوە جێبەجێکرا. بۆ لابردنی ناوەڕۆکەکە لە فایلەکە پاشەکەوتی بکە. 	JavaScript Console	کۆنسۆڵی JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript کوژاوەتەوە (هەڵبژاردەکان ▸ JavaScript).+	JavaScript is turned off by your administrator.	JavaScript لەلایەن بەڕێوەبەرەکەتەوە کوژاوەتەوە.+	Turned off by your administrator.	لەلایەن بەڕێوەبەرەکەتەوە کوژاوەتەوە.+	Turned off because your administrator's policy couldn't be read.	کوژاوەتەوە چونکە سیاسەتی بەڕێوەبەرەکەت نەخوێنرایەوە. 	Output appears here.	دەرهاویشتەکە لێرە دەردەکەوێت. 	Script Name:	ناوی سکریپت: 	This document has no document-level scripts.	ئەم بەڵگەنامەیە هیچ سکریپتێکی لە ئاستی بەڵگەنامەدا نییە.diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv--- a/crates/ui-egui/src/i18n/cs.tsv+++ b/crates/ui-egui/src/i18n/cs.tsv@@ -1358,6 +1358,9 @@ key	Space	Mezerník 	Applied {n} redaction marks. Save to remove the content from the file.	Použito značek začernění: {n}. Uložením odstraníte obsah ze souboru. 	JavaScript Console	Konzole JavaScriptu 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript je vypnutý („Předvolby > JavaScript“).+	JavaScript is turned off by your administrator.	JavaScript vypnul váš správce.+	Turned off by your administrator.	Vypnuto vaším správcem.+	Turned off because your administrator's policy couldn't be read.	Vypnuto, protože zásadu vašeho správce nelze přečíst. 	Output appears here.	Zde se zobrazí výstup. 	Script Name:	Název skriptu: 	This document has no document-level scripts.	Tento dokument nemá žádné skripty na úrovni dokumentu.diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv--- a/crates/ui-egui/src/i18n/de.tsv+++ b/crates/ui-egui/src/i18n/de.tsv@@ -1272,6 +1272,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} Schwärzungsmarkierungen angewendet. Speichere, um den Inhalt aus der Datei zu entfernen. 	JavaScript Console	JavaScript-Konsole 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript ist ausgeschaltet (Einstellungen > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript wurde von Ihrem Administrator ausgeschaltet.+	Turned off by your administrator.	Von Ihrem Administrator ausgeschaltet.+	Turned off because your administrator's policy couldn't be read.	Ausgeschaltet, weil die Richtlinie Ihres Administrators nicht gelesen werden konnte. 	Output appears here.	Die Ausgabe erscheint hier. 	Script Name:	Skriptname: 	This document has no document-level scripts.	Dieses Dokument hat keine Skripte auf Dokumentebene.diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv--- a/crates/ui-egui/src/i18n/el.tsv+++ b/crates/ui-egui/src/i18n/el.tsv@@ -814,6 +814,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	Εφαρμόστηκαν {n} επισημάνσεις απόκρυψης. Αποθηκεύστε για να αφαιρεθεί το περιεχόμενο από το αρχείο. 	JavaScript Console	Κονσόλα JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	Η JavaScript είναι απενεργοποιημένη (Προτιμήσεις > JavaScript).+	JavaScript is turned off by your administrator.	Η JavaScript έχει απενεργοποιηθεί από τον διαχειριστή σας.+	Turned off by your administrator.	Απενεργοποιήθηκε από τον διαχειριστή σας.+	Turned off because your administrator's policy couldn't be read.	Απενεργοποιήθηκε επειδή δεν ήταν δυνατή η ανάγνωση της πολιτικής του διαχειριστή σας. 	Output appears here.	Η έξοδος εμφανίζεται εδώ. 	Script Name:	Όνομα σεναρίου: 	This document has no document-level scripts.	Αυτό το έγγραφο δεν περιέχει σενάρια σε επίπεδο εγγράφου.diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv--- a/crates/ui-egui/src/i18n/es.tsv+++ b/crates/ui-egui/src/i18n/es.tsv@@ -1281,6 +1281,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	Se han aplicado {n} marcas de censura. Guarda para eliminar el contenido del archivo. 	JavaScript Console	Consola de JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript está desactivado (Preferencias > JavaScript).+	JavaScript is turned off by your administrator.	Su administrador ha desactivado JavaScript.+	Turned off by your administrator.	Desactivado por su administrador.+	Turned off because your administrator's policy couldn't be read.	Desactivado porque no se pudo leer la directiva de su administrador. 	Output appears here.	La salida aparece aquí. 	Script Name:	Nombre del script: 	This document has no document-level scripts.	Este documento no tiene scripts de nivel de documento.diff --git a/crates/ui-egui/src/i18n/fa.tsv b/crates/ui-egui/src/i18n/fa.tsv--- a/crates/ui-egui/src/i18n/fa.tsv+++ b/crates/ui-egui/src/i18n/fa.tsv@@ -1282,6 +1282,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} نشانه سیاه‌کردن اعمال شد. برای حذف محتوا از فایل ذخیره کنید. 	JavaScript Console	کنسول JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript خاموش است (ترجیحات ▸ JavaScript).+	JavaScript is turned off by your administrator.	JavaScript توسط سرپرست شما خاموش شده است.+	Turned off by your administrator.	توسط سرپرست شما خاموش شده است.+	Turned off because your administrator's policy couldn't be read.	خاموش است، چون خط‌مشی سرپرست شما خوانده نشد. 	Output appears here.	خروجی اینجا نمایش داده می‌شود. 	Script Name:	نام اسکریپت: 	This document has no document-level scripts.	این سند اسکریپت سطح سند ندارد.diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv--- a/crates/ui-egui/src/i18n/fi.tsv+++ b/crates/ui-egui/src/i18n/fi.tsv@@ -1236,6 +1236,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	Käytettiin {n} peittomerkintää. Poista sisältö tiedostosta tallentamalla. 	JavaScript Console	JavaScript-konsoli 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript on poistettu käytöstä (Asetukset > JavaScript).+	JavaScript is turned off by your administrator.	Järjestelmänvalvoja on poistanut JavaScriptin käytöstä.+	Turned off by your administrator.	Järjestelmänvalvoja on poistanut käytöstä.+	Turned off because your administrator's policy couldn't be read.	Poistettu käytöstä, koska järjestelmänvalvojan käytäntöä ei voitu lukea. 	Output appears here.	Tuloste näkyy tässä. 	Script Name:	Komentosarjan nimi: 	This document has no document-level scripts.	Tässä asiakirjassa ei ole asiakirjatason komentosarjoja.diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv--- a/crates/ui-egui/src/i18n/fr.tsv+++ b/crates/ui-egui/src/i18n/fr.tsv@@ -1267,6 +1267,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} marques de biffure appliquées. Enregistrez pour supprimer le contenu du fichier. 	JavaScript Console	Console JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript est désactivé (Préférences > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript a été désactivé par votre administrateur.+	Turned off by your administrator.	Désactivé par votre administrateur.+	Turned off because your administrator's policy couldn't be read.	Désactivé, car la stratégie de votre administrateur n'a pas pu être lue. 	Output appears here.	La sortie apparaît ici. 	Script Name:	Nom du script : 	This document has no document-level scripts.	Ce document ne contient aucun script au niveau du document.diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv--- a/crates/ui-egui/src/i18n/hi.tsv+++ b/crates/ui-egui/src/i18n/hi.tsv@@ -1219,6 +1219,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} रेडैक्शन चिह्न लागू किए गए। सामग्री फ़ाइल से हटाने के लिए सहेजें। 	JavaScript Console	JavaScript कंसोल 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript बंद है (प्राथमिकताएँ ▸ JavaScript)।+	JavaScript is turned off by your administrator.	आपके व्यवस्थापक ने JavaScript बंद किया है।+	Turned off by your administrator.	आपके व्यवस्थापक ने बंद किया है।+	Turned off because your administrator's policy couldn't be read.	बंद है, क्योंकि आपके व्यवस्थापक की नीति पढ़ी नहीं जा सकी। 	Output appears here.	आउटपुट यहाँ दिखता है। 	Script Name:	स्क्रिप्ट का नाम: 	This document has no document-level scripts.	इस दस्तावेज़ में दस्तावेज़-स्तर की कोई स्क्रिप्ट नहीं है।diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv--- a/crates/ui-egui/src/i18n/hu.tsv+++ b/crates/ui-egui/src/i18n/hu.tsv@@ -1257,6 +1257,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} kitakarásjelölést alkalmaztunk. Ments, hogy a tartalom kikerüljön a fájlból. 	JavaScript Console	JavaScript-konzol 	JavaScript is turned off (Preferences ▸ JavaScript).	A JavaScript ki van kapcsolva (Beállítások > JavaScript).+	JavaScript is turned off by your administrator.	A JavaScriptet a rendszergazda kikapcsolta.+	Turned off by your administrator.	A rendszergazda kikapcsolta.+	Turned off because your administrator's policy couldn't be read.	Kikapcsolva, mert a rendszergazdai házirend nem olvasható. 	Output appears here.	A kimenet itt jelenik meg. 	Script Name:	Szkript neve: 	This document has no document-level scripts.	Ebben a dokumentumban nincs dokumentumszintű szkript.diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv--- a/crates/ui-egui/src/i18n/id.tsv+++ b/crates/ui-egui/src/i18n/id.tsv@@ -654,6 +654,9 @@ 	Its security settings don't allow copying pages	Pengaturan keamanannya tidak mengizinkan penyalinan halaman 	JavaScript Console	Konsol JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript dimatikan (Preferensi > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript dimatikan oleh administrator Anda.+	Turned off by your administrator.	Dimatikan oleh administrator Anda.+	Turned off because your administrator's policy couldn't be read.	Dimatikan karena kebijakan administrator Anda tidak dapat dibaca. 	JavaScript, e.g. getField("total").value	JavaScript, misalnya getField("total").value 	Join our Discord	Gabung ke Discord kami 	Join the ArtCraft community	Gabung ke komunitas ArtCraftdiff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv--- a/crates/ui-egui/src/i18n/it.tsv+++ b/crates/ui-egui/src/i18n/it.tsv@@ -1283,6 +1283,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	Applicati {n} segni di oscuramento. Salva per rimuovere il contenuto dal file. 	JavaScript Console	Console JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript è disattivato (Preferenze > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript è stato disattivato dall'amministratore.+	Turned off by your administrator.	Disattivato dall'amministratore.+	Turned off because your administrator's policy couldn't be read.	Disattivato perché non è stato possibile leggere i criteri dell'amministratore. 	Output appears here.	L'output compare qui. 	Script Name:	Nome script: 	This document has no document-level scripts.	Questo documento non ha script a livello di documento.diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv--- a/crates/ui-egui/src/i18n/ja.tsv+++ b/crates/ui-egui/src/i18n/ja.tsv@@ -1287,6 +1287,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	墨消しマークを {n} 件適用しました。ファイルから内容を除去するには保存してください。 	JavaScript Console	JavaScript コンソール 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript は無効です(環境設定 → JavaScript)。+	JavaScript is turned off by your administrator.	JavaScript は管理者によって無効にされています。+	Turned off by your administrator.	管理者によって無効にされています。+	Turned off because your administrator's policy couldn't be read.	管理者のポリシーを読み取れなかったため、無効になっています。 	Output appears here.	出力はここに表示されます。 	Script Name:	スクリプト名: 	This document has no document-level scripts.	この文書に文書レベルのスクリプトはありません。diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv--- a/crates/ui-egui/src/i18n/kk.tsv+++ b/crates/ui-egui/src/i18n/kk.tsv@@ -1231,6 +1231,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} жасыру белгісі қолданылды. Мазмұнды файлдан алып тастау үшін сақтаңыз. 	JavaScript Console	JavaScript консолі 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript өшірулі (Параметрлер > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript-ті әкімшіңіз өшірген.+	Turned off by your administrator.	Әкімшіңіз өшірген.+	Turned off because your administrator's policy couldn't be read.	Әкімшіңіздің саясатын оқу мүмкін болмағандықтан өшірілген. 	Output appears here.	Шығыс осында пайда болады. 	Script Name:	Сценарийдің атауы: 	This document has no document-level scripts.	Бұл құжатта құжат деңгейіндегі сценарийлер жоқ.diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv--- a/crates/ui-egui/src/i18n/ko.tsv+++ b/crates/ui-egui/src/i18n/ko.tsv@@ -1219,6 +1219,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	제거 대상 표시 {n}개가 적용되었습니다. 파일에서 콘텐츠를 제거하려면 저장하세요. 	JavaScript Console	JavaScript 콘솔 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript가 꺼져 있습니다(기본 설정 ▸ JavaScript).+	JavaScript is turned off by your administrator.	관리자가 JavaScript를 껐습니다.+	Turned off by your administrator.	관리자가 껐습니다.+	Turned off because your administrator's policy couldn't be read.	관리자 정책을 읽을 수 없어 꺼져 있습니다. 	Output appears here.	출력이 여기에 표시됩니다. 	Script Name:	스크립트 이름: 	This document has no document-level scripts.	이 문서에는 문서 수준 스크립트가 없습니다.diff --git a/crates/ui-egui/src/i18n/lo.tsv b/crates/ui-egui/src/i18n/lo.tsv--- a/crates/ui-egui/src/i18n/lo.tsv+++ b/crates/ui-egui/src/i18n/lo.tsv@@ -1253,6 +1253,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	ນຳໃຊ້ເຄື່ອງໝາຍປິດບັງ {n} ລາຍການແລ້ວ. ບັນທຶກເພື່ອລຶບເນື້ອໃນອອກຈາກໄຟລ໌. 	JavaScript Console	ຄອນໂຊລ JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript ຖືກປິດຢູ່ (ການຕັ້ງຄ່າ ▸ JavaScript).+	JavaScript is turned off by your administrator.	JavaScript ຖືກປິດໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ.+	Turned off by your administrator.	ຖືກປິດໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ.+	Turned off because your administrator's policy couldn't be read.	ຖືກປິດ ເພາະບໍ່ສາມາດອ່ານນະໂຍບາຍຂອງຜູ້ເບິ່ງແຍງລະບົບໄດ້. 	Output appears here.	ຜົນອອກຈະສະແດງຢູ່ນີ້. 	Script Name:	ຊື່ສະຄຣິບ: 	This document has no document-level scripts.	ເອກະສານນີ້ບໍ່ມີສະຄຣິບລະດັບເອກະສານ.diff --git a/crates/ui-egui/src/i18n/lt.tsv b/crates/ui-egui/src/i18n/lt.tsv--- a/crates/ui-egui/src/i18n/lt.tsv+++ b/crates/ui-egui/src/i18n/lt.tsv@@ -672,6 +672,9 @@ 	Its security settings don't allow copying pages	Saugos nuostatos neleidžia kopijuoti puslapių 	JavaScript Console	JavaScript konsolė 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript išjungtas (Nuostatos > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript išjungė jūsų administratorius.+	Turned off by your administrator.	Išjungė jūsų administratorius.+	Turned off because your administrator's policy couldn't be read.	Išjungta, nes nepavyko perskaityti jūsų administratoriaus strategijos. 	JavaScript, e.g. getField("total").value	JavaScript, pvz., getField("total").value 	Join our Discord	Prisijunkite prie mūsų „Discord“ serverio 	Join the ArtCraft community	Prisijunkite prie „ArtCraft“ bendruomenėsdiff --git a/crates/ui-egui/src/i18n/nb.tsv b/crates/ui-egui/src/i18n/nb.tsv--- a/crates/ui-egui/src/i18n/nb.tsv+++ b/crates/ui-egui/src/i18n/nb.tsv@@ -663,6 +663,9 @@ 	Its security settings don't allow copying pages	Sikkerhetsinnstillingene tillater ikke kopiering av sider 	JavaScript Console	JavaScript-konsoll 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript er slått av (Innstillinger > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript er slått av av administratoren din.+	Turned off by your administrator.	Slått av av administratoren din.+	Turned off because your administrator's policy couldn't be read.	Slått av fordi policyen fra administratoren din ikke kunne leses. 	JavaScript, e.g. getField("total").value	JavaScript, f.eks. getField("total").value 	Join our Discord	Bli med på Discord-serveren vår 	Join the ArtCraft community	Bli med i ArtCraft-fellesskapetdiff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv--- a/crates/ui-egui/src/i18n/pl.tsv+++ b/crates/ui-egui/src/i18n/pl.tsv@@ -1270,6 +1270,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	Zastosowano oznaczenia do utajnienia w liczbie {n}. Zapisz, aby usunąć zawartość z pliku. 	JavaScript Console	Konsola JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript jest wyłączony (Preferencje ▸ JavaScript).+	JavaScript is turned off by your administrator.	JavaScript został wyłączony przez administratora.+	Turned off by your administrator.	Wyłączone przez administratora.+	Turned off because your administrator's policy couldn't be read.	Wyłączone, ponieważ nie można odczytać zasad administratora. 	Output appears here.	Tutaj pojawią się wyniki. 	Script Name:	Nazwa skryptu: 	This document has no document-level scripts.	Ten dokument nie zawiera skryptów na poziomie dokumentu.diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv--- a/crates/ui-egui/src/i18n/pt-br.tsv+++ b/crates/ui-egui/src/i18n/pt-br.tsv@@ -902,6 +902,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} marcas de ocultação aplicadas. Salve para remover o conteúdo do arquivo. 	JavaScript Console	Console JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	O JavaScript está desativado (Preferências > JavaScript).+	JavaScript is turned off by your administrator.	O JavaScript foi desativado pelo seu administrador.+	Turned off by your administrator.	Desativado pelo seu administrador.+	Turned off because your administrator's policy couldn't be read.	Desativado porque não foi possível ler a política do seu administrador. 	Output appears here.	A saída aparece aqui. 	Document JavaScripts	JavaScripts do documento 	Script Name:	Nome do script:diff --git a/crates/ui-egui/src/i18n/ro.tsv b/crates/ui-egui/src/i18n/ro.tsv--- a/crates/ui-egui/src/i18n/ro.tsv+++ b/crates/ui-egui/src/i18n/ro.tsv@@ -663,6 +663,9 @@ 	Its security settings don't allow copying pages	Setările sale de securitate nu permit copierea paginilor 	JavaScript Console	Consolă JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript este dezactivat (Preferințe > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript a fost dezactivat de administratorul dvs.+	Turned off by your administrator.	Dezactivat de administratorul dvs.+	Turned off because your administrator's policy couldn't be read.	Dezactivat, deoarece politica administratorului dvs. nu a putut fi citită. 	JavaScript, e.g. getField("total").value	JavaScript, de ex. getField("total").value 	Join our Discord	Intrați pe Discord-ul nostru 	Join the ArtCraft community	Alăturați-vă comunității ArtCraftdiff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv--- a/crates/ui-egui/src/i18n/ru.tsv+++ b/crates/ui-egui/src/i18n/ru.tsv@@ -1287,6 +1287,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	Применено {n} меток сокрытия. Сохраните, чтобы удалить содержимое из файла. 	JavaScript Console	Консоль JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript выключен (Настройки > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript отключён вашим администратором.+	Turned off by your administrator.	Отключено вашим администратором.+	Turned off because your administrator's policy couldn't be read.	Отключено, так как не удалось прочитать политику вашего администратора. 	Output appears here.	Вывод появляется здесь. 	Script Name:	Название скрипта: 	This document has no document-level scripts.	В этом документе нет скриптов уровня документа.diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv--- a/crates/ui-egui/src/i18n/sl.tsv+++ b/crates/ui-egui/src/i18n/sl.tsv@@ -989,6 +989,9 @@ 	JavaScript Console	Konzola JavaScript 	JavaScript console…	Konzola JavaScript… 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript je izklopljen (Nastavitve > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript je izklopil vaš skrbnik.+	Turned off by your administrator.	Izklopil vaš skrbnik.+	Turned off because your administrator's policy couldn't be read.	Izklopljeno, ker pravilnika vašega skrbnika ni bilo mogoče prebrati. 	JavaScript, e.g. getField("total").value	JavaScript, npr. getField("total").value 	Join our Discord	Discord kanal 	Join the ArtCraft Discord	Pridružite se Discordu ArtCraftdiff --git a/crates/ui-egui/src/i18n/sq.tsv b/crates/ui-egui/src/i18n/sq.tsv--- a/crates/ui-egui/src/i18n/sq.tsv+++ b/crates/ui-egui/src/i18n/sq.tsv@@ -976,6 +976,9 @@ 	JavaScript Console	Konsola JavaScript 	JavaScript console…	Konsola JavaScript… 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript është çaktivizuar (Preferencat > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript është çaktivizuar nga administratori juaj.+	Turned off by your administrator.	Çaktivizuar nga administratori juaj.+	Turned off because your administrator's policy couldn't be read.	Çaktivizuar, sepse politika e administratorit tuaj nuk mund të lexohej. 	JavaScript, e.g. getField("total").value	JavaScript, p.sh. getField("total").value 	Join our Discord	Bashkohu me Discord-in tonë 	Join the ArtCraft Discord	Bashkohu me Discord-in e ArtCraftdiff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv--- a/crates/ui-egui/src/i18n/te.tsv+++ b/crates/ui-egui/src/i18n/te.tsv@@ -1257,6 +1257,9 @@ comment menu	Edit	సవరించు 	Applied {n} redaction marks. Save to remove the content from the file.	{n} రెడాక్షన్ గుర్తులు వర్తింపజేయబడ్డాయి. కంటెంట్‌ను ఫైల్ నుండి తీసివేయడానికి సేవ్ చేయండి. 	JavaScript Console	JavaScript కన్సోల్ 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript ఆఫ్ చేయబడింది (ప్రాధాన్యతలు > JavaScript).+	JavaScript is turned off by your administrator.	మీ నిర్వాహకుడు JavaScriptను ఆఫ్ చేశారు.+	Turned off by your administrator.	మీ నిర్వాహకుడు ఆఫ్ చేశారు.+	Turned off because your administrator's policy couldn't be read.	మీ నిర్వాహకుని విధానాన్ని చదవలేకపోయినందున ఆఫ్ చేయబడింది. 	Output appears here.	అవుట్‌పుట్ ఇక్కడ కనిపిస్తుంది. 	Script Name:	స్క్రిప్ట్ పేరు: 	This document has no document-level scripts.	ఈ పత్రంలో పత్రం-స్థాయి స్క్రిప్ట్‌లు ఏవీ లేవు.diff --git a/crates/ui-egui/src/i18n/th.tsv b/crates/ui-egui/src/i18n/th.tsv--- a/crates/ui-egui/src/i18n/th.tsv+++ b/crates/ui-egui/src/i18n/th.tsv@@ -1230,6 +1230,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	ปิดทับตามเครื่องหมายแล้ว {n} จุด บันทึกเพื่อเอาเนื้อหาออกจากไฟล์ 	JavaScript Console	คอนโซล JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript ปิดอยู่ (การตั้งค่า ▸ JavaScript)+	JavaScript is turned off by your administrator.	ผู้ดูแลระบบของคุณปิด JavaScript ไว้+	Turned off by your administrator.	ผู้ดูแลระบบของคุณปิดไว้+	Turned off because your administrator's policy couldn't be read.	ปิดอยู่ เพราะอ่านนโยบายของผู้ดูแลระบบไม่ได้ 	Output appears here.	ผลลัพธ์จะแสดงที่นี่ 	Script Name:	ชื่อสคริปต์: 	This document has no document-level scripts.	เอกสารนี้ไม่มีสคริปต์ระดับเอกสารdiff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv--- a/crates/ui-egui/src/i18n/tr.tsv+++ b/crates/ui-egui/src/i18n/tr.tsv@@ -1244,6 +1244,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	{n} karartma işareti uygulandı. İçeriği dosyadan kaldırmak için kaydedin. 	JavaScript Console	JavaScript Konsolu 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript kapalı (Tercihler > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript yöneticiniz tarafından kapatıldı.+	Turned off by your administrator.	Yöneticiniz tarafından kapatıldı.+	Turned off because your administrator's policy couldn't be read.	Yöneticinizin ilkesi okunamadığı için kapatıldı. 	Output appears here.	Çıktı burada görünür. 	Script Name:	Komut Dosyası Adı: 	This document has no document-level scripts.	Bu belgede belge düzeyinde komut dosyası yok.diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv--- a/crates/ui-egui/src/i18n/uk.tsv+++ b/crates/ui-egui/src/i18n/uk.tsv@@ -1284,6 +1284,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	Застосовано позначок вилучення: {n}. Збережіть, щоб видалити вміст із файлу. 	JavaScript Console	Консоль JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript вимкнено («Налаштування > JavaScript»).+	JavaScript is turned off by your administrator.	JavaScript вимкнено вашим адміністратором.+	Turned off by your administrator.	Вимкнено вашим адміністратором.+	Turned off because your administrator's policy couldn't be read.	Вимкнено, оскільки не вдалося прочитати політику вашого адміністратора. 	Output appears here.	Тут з’являється вивід. 	Script Name:	Назва сценарію: 	This document has no document-level scripts.	Цей документ не має сценаріїв рівня документа.diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv--- a/crates/ui-egui/src/i18n/vi.tsv+++ b/crates/ui-egui/src/i18n/vi.tsv@@ -668,6 +668,9 @@ theme	Appearance	Giao diện 	Its security settings don't allow copying pages	Cài đặt bảo mật của nó không cho phép sao chép trang 	JavaScript Console	Bảng điều khiển JavaScript 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript đã bị tắt (Tùy chọn > JavaScript).+	JavaScript is turned off by your administrator.	JavaScript đã bị quản trị viên của bạn tắt.+	Turned off by your administrator.	Đã bị quản trị viên của bạn tắt.+	Turned off because your administrator's policy couldn't be read.	Đã tắt vì không đọc được chính sách của quản trị viên. 	JavaScript, e.g. getField("total").value	JavaScript, ví dụ getField("total").value 	Join our Discord	Tham gia Discord của chúng tôi 	Join the ArtCraft community	Tham gia cộng đồng ArtCraftdiff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv--- a/crates/ui-egui/src/i18n/zh-hans.tsv+++ b/crates/ui-egui/src/i18n/zh-hans.tsv@@ -962,6 +962,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	已应用 {n} 个密文标记。保存以从文件中删除内容。 	JavaScript Console	JavaScript 控制台 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript 已关闭(首选项 ▸ JavaScript)。+	JavaScript is turned off by your administrator.	JavaScript 已被管理员关闭。+	Turned off by your administrator.	已被管理员关闭。+	Turned off because your administrator's policy couldn't be read.	无法读取管理员的策略,因此已关闭。 	Output appears here.	输出显示在此处。 	Document JavaScripts	文档 JavaScript 	Script Name:	脚本名称:diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv--- a/crates/ui-egui/src/i18n/zh-hant.tsv+++ b/crates/ui-egui/src/i18n/zh-hant.tsv@@ -906,6 +906,9 @@ 	Applied {n} redaction marks. Save to remove the content from the file.	已套用 {n} 個遮蔽內容標記。儲存以從檔案中刪除內容。 	JavaScript Console	JavaScript 主控台 	JavaScript is turned off (Preferences ▸ JavaScript).	JavaScript 已關閉(偏好設定 → JavaScript)。+	JavaScript is turned off by your administrator.	JavaScript 已被系統管理員關閉。+	Turned off by your administrator.	已被系統管理員關閉。+	Turned off because your administrator's policy couldn't be read.	無法讀取系統管理員的原則,因此已關閉。 	Output appears here.	輸出顯示在此處。 	Document JavaScripts	文件 JavaScript 	Script Name:	指令碼名稱:diff --git a/crates/ui-egui/src/workflows/actions_ui.rs b/crates/ui-egui/src/workflows/actions_ui.rs--- a/crates/ui-egui/src/workflows/actions_ui.rs+++ b/crates/ui-egui/src/workflows/actions_ui.rs@@ -94,6 +94,8 @@ impl PdfCraftApp {         }         let progress = Arc::new(Mutex::new(RunProgress { total: files.len(), ..Default::default() }));         let p = progress.clone();+        // Each file runs in a session of its own: it takes this session's JavaScript setting.+        let scripting = self.session.scripting();         // The summary is written on the worker thread: draw it in the UI's language.         let lang = crate::i18n::current();         let work = move || {@@ -103,7 +105,7 @@ impl PdfCraftApp {                 if let Ok(mut s) = p.lock() {                     s.done = i;                 }-                let r = pdfcraft_engine::actions::run_on(&action, &name, Arc::new(bytes), |_, _| {}).and_then(|r| {+                let r = pdfcraft_engine::actions::run_on(&action, &name, Arc::new(bytes), &scripting, |_, _| {}).and_then(|r| {                     #[cfg(not(target_arch = "wasm32"))]                     write_result(&dir, &name, &r.bytes).map_err(|e| e.to_string())?;                     #[cfg(target_arch = "wasm32")]diff --git a/crates/ui-egui/src/workflows/js_ui.rs b/crates/ui-egui/src/workflows/js_ui.rs--- a/crates/ui-egui/src/workflows/js_ui.rs+++ b/crates/ui-egui/src/workflows/js_ui.rs@@ -29,6 +29,21 @@ pub(crate) fn scripts_off_notice() -> String { }  impl PdfCraftApp {+    /// Apply the machine's administrator policies (`pdfcraft_platform::policy`): read once, at+    /// launch, after saved preferences are restored. Not in tests, which set the lock+    /// themselves, and not on the web, which has no machine policy.+    #[cfg(not(target_arch = "wasm32"))]+    pub fn apply_admin_policy(&mut self) {+        let lock = pdfcraft_platform::policy::Policy::load().javascript_lock();+        if let Some((source, unreadable)) = &lock {+            match unreadable {+                Some(why) => log::warn!("JavaScript is off: the policy at {source} couldn't be read ({why})"),+                None => log::info!("JavaScript is off: turned off by the policy at {source}"),+            }+        }+        self.session.set_javascript_lock(lock.map(|(source, unreadable)| pdfcraft_engine::js::JavaScriptLock { source, unreadable }));+    }+     /// Act on what scripts produced in document `id`: alerts are shown, console output goes to     /// the console, print and page requests are carried out, links wait for the user's permission     /// (form submissions are reported, never sent).@@ -180,7 +195,9 @@ fn buttons(ui: &mut egui::Ui, primary: &str, others: &[&str]) -> Option<String> pub(crate) fn console_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {     ui.label(egui::RichText::new(tl!("JavaScript Console")).font(theme::semibold(18.0)));     ui.add_space(6.0);-    if !app.session.javascript() {+    if app.session.javascript_lock().is_some() {+        ui.label(egui::RichText::new(tl!("JavaScript is turned off by your administrator.")).small().color(t.text_muted));+    } else if !app.session.javascript() {         ui.label(egui::RichText::new(tl!("JavaScript is turned off (Preferences ▸ JavaScript).")).small().color(t.text_muted));     }     egui::Frame::new().fill(t.hover).corner_radius(egui::CornerRadius::same(6)).inner_margin(egui::Margin::same(8)).show(ui, |ui| {@@ -561,9 +578,26 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok     ui.label(egui::RichText::new("JavaScript").font(theme::semibold(13.0)));     egui::Frame::new().fill(t.hover).corner_radius(egui::CornerRadius::same(6)).inner_margin(egui::Margin::same(10)).show(ui, |ui| {         ui.set_width(ui.available_width());-        let mut on = app.session.javascript();-        if ui.add(widgets::checkbox(&mut on, tl!("Enable Acrobat JavaScript"))).changed() {-            app.session.set_javascript(on);+        if let Some(lock) = app.session.javascript_lock().cloned() {+            // An administrator's policy: shown off and locked, with where it comes from. The+            // person's own choice is kept for when the policy goes away.+            let mut off = false;+            ui.add_enabled(false, widgets::checkbox(&mut off, tl!("Enable Acrobat JavaScript")));+            let note = match lock.unreadable {+                None => tl!("Turned off by your administrator.").to_string(),+                Some(_) => tl!("Turned off because your administrator's policy couldn't be read.").to_string(),+            };+            ui.label(egui::RichText::new(note).small());+            let detail = match &lock.unreadable {+                Some(why) => format!("{}: {why}", lock.source),+                None => lock.source.clone(),+            };+            ui.label(egui::RichText::new(detail).small().monospace().color(t.text_muted));+        } else {+            let mut on = app.session.javascript();+            if ui.add(widgets::checkbox(&mut on, tl!("Enable Acrobat JavaScript"))).changed() {+                app.session.set_javascript(on);+            }         }         ui.label(             egui::RichText::new(tl!("Scripts run in a sandbox without file or network access. A script that asks to open a web page needs your permission first. With JavaScript off, Acrobat's standard format, validate and calculate functions still work."))diff --git a/crates/ui-egui/src/workflows/ocr_ui.rs b/crates/ui-egui/src/workflows/ocr_ui.rs--- a/crates/ui-egui/src/workflows/ocr_ui.rs+++ b/crates/ui-egui/src/workflows/ocr_ui.rs@@ -330,15 +330,17 @@ impl PdfCraftApp {         }         let progress = Arc::new(Mutex::new(BatchProgress { total: files.len(), ..Default::default() }));         let p = progress.clone();+        // Each file opens in a session of its own: it takes this session's JavaScript setting.+        let scripting = self.session.scripting();         // The summary is written on the worker thread: draw it in the UI's language.         let lang = crate::i18n::current();         let work = move || {             crate::i18n::set_current(lang);             // A panic in the recognizer still ends the batch with a summary (#3919).             #[cfg(not(target_arch = "wasm32"))]-            let summary = pdfcraft_engine::guard(|| recognize_files(files, &settings, &p, &dir));+            let summary = pdfcraft_engine::guard(|| recognize_files(files, &settings, &scripting, &p, &dir));             #[cfg(target_arch = "wasm32")]-            let summary = pdfcraft_engine::guard(|| recognize_files(files, &settings, &p));+            let summary = pdfcraft_engine::guard(|| recognize_files(files, &settings, &scripting, &p));             let summary = summary.unwrap_or_else(|panic| failed(&panic));             if let Ok(mut s) = p.lock() {                 s.done = s.total;@@ -362,6 +364,7 @@ impl PdfCraftApp { fn recognize_files(     files: Vec<(String, Vec<u8>)>,     settings: &OcrSettings,+    scripting: &pdfcraft_engine::js::Scripting,     p: &Mutex<BatchProgress>,     #[cfg(not(target_arch = "wasm32"))] dir: &std::path::Path, ) -> String {@@ -373,7 +376,7 @@ fn recognize_files(                 if let Ok(mut s) = p.lock() {                     s.done = i;                 }-                let r = pdfcraft_engine::ocr::recognize_file(&name, Arc::new(bytes), None, settings.clone(), &ocr, |_, _| true);+                let r = pdfcraft_engine::ocr::recognize_file(&name, Arc::new(bytes), None, settings.clone(), &ocr, scripting, |_, _| true);                 let saved = r.and_then(|r| {                     #[cfg(not(target_arch = "wasm32"))]                     crate::editing::write_atomically(&dir.join(&name).to_string_lossy(), &r.bytes).map_err(|e| e.to_string())?;diff --git a/crates/ui-egui/tests/it/javascript.rs b/crates/ui-egui/tests/it/javascript.rs--- a/crates/ui-egui/tests/it/javascript.rs+++ b/crates/ui-egui/tests/it/javascript.rs@@ -2,7 +2,7 @@ //! script and Preferences ▸ JavaScript.  use egui_kittest::Harness;-use egui_kittest::kittest::Queryable;+use egui_kittest::kittest::{NodeT, Queryable}; use pdfcraft_ui_egui::{Dialog, PdfCraftApp};  /// A form with a text field and a push button whose Mouse Up script fills it in.@@ -107,6 +107,54 @@ fn document_scripts_and_preferences() {     assert!(!fresh.session.javascript(), "the preference is remembered"); } +#[test]+fn an_administrators_policy_locks_the_preference_off() {+    let mut h = harness();+    let source = r"HKLM\SOFTWARE\Policies\PdfCraft\DisableJavaScript";+    h.state_mut().session.set_javascript_lock(Some(pdfcraft_engine::js::JavaScriptLock { source: source.into(), unreadable: None }));+    h.state_mut().dialog = Some(Dialog::Preferences);+    h.run_steps(2);+    let checkbox = h.get_by_label("Enable Acrobat JavaScript");+    assert!(checkbox.accesskit_node().is_disabled(), "locked");+    assert_eq!(checkbox.accesskit_node().toggled(), Some(egui::accesskit::Toggled::False), "shown off");+    h.get_by_label("Turned off by your administrator.");+    h.get_by_label(source);+    // Clicking the locked box changes nothing, and the button script doesn't run.+    h.get_by_label("Enable Acrobat JavaScript").click();+    h.run_steps(2);+    assert!(!h.state().session.javascript());+    let id = h.state().active_ids().unwrap().1;+    h.state_mut().run_button_script(id, "hello", "getField('greeting').value = 'ran';");+    h.run_steps(2);+    assert_ne!(value(&h, "greeting"), ["ran"]);+    // The console says why.+    h.state_mut().dialog = None;+    assert!(h.state_mut().execute("tools.js_console"));+    h.run_steps(2);+    h.get_by_label("JavaScript is turned off by your administrator.");++    // What's saved is the person's own choice (on), so it comes back once the policy is gone.+    let saved = h.state().persist();+    let mut fresh = PdfCraftApp::new();+    fresh.restore(&saved);+    assert!(fresh.session.javascript(), "the policy isn't saved as the preference");+}++#[test]+fn an_unreadable_policy_says_so() {+    let mut h = harness();+    let lock = pdfcraft_engine::js::JavaScriptLock {+        source: "/etc/pdfcraft/policy.json (DisableJavaScript)".into(),+        unreadable: Some("it isn't valid JSON".into()),+    };+    h.state_mut().session.set_javascript_lock(Some(lock));+    h.state_mut().dialog = Some(Dialog::Preferences);+    h.run_steps(2);+    assert!(h.get_by_label("Enable Acrobat JavaScript").accesskit_node().is_disabled());+    h.get_by_label("Turned off because your administrator's policy couldn't be read.");+    h.get_by_label("/etc/pdfcraft/policy.json (DisableJavaScript): it isn't valid JSON");+}+ #[test] fn merge_data_files_into_a_spreadsheet() {     let dir = std::env::temp_dir().join(format!("pdfcraft-merge-ui-{}", std::process::id()));diff --git a/docs/administrators.md b/docs/administrators.mdnew file mode 100644--- /dev/null+++ b/docs/administrators.md@@ -0,0 +1,82 @@+# Policies for administrators++An organisation can set some of PdfCraft's settings for everyone on a machine, in the places its+management tools already write: Group Policy, Intune or an RMM's `reg add` on Windows, a+configuration profile from an MDM on macOS, and a file under `/etc` on Linux and the BSDs. A+policy wins over Preferences, and people can't change it: the setting shows as off and locked,+with a note saying it was set by their administrator and where.++PdfCraft reads policies when it starts, so a change applies once every PdfCraft window has been+closed and it is started again (opening another file while it runs hands the file to the running+copy). With no policy, nothing changes. The desktop app and `pdfcraft-cli` (including `run` and+`mcp`) both follow them, and so do files processed in the background: the Action Wizard,+multi-file OCR and `doc_batch`.++## Policies++| Name | Type | Effect |+|---|---|---|+| `DisableJavaScript` | flag | `1` (or `true`) turns JavaScript in documents off and keeps it off: field scripts, calculations, validation, button and link scripts, the Action Wizard's "Run a JavaScript" step, the JavaScript console, and the scripts of XFA forms (JavaScript and FormCalc). Acrobat's standard format, validate and calculate functions (`AFNumber_Format` and the rest) keep working, as they do with JavaScript turned off in Preferences. `0` (or `false`) leaves the choice to the person using the app. It is the equivalent of Acrobat's `bDisableJavaScript`. |++A flag is the number `0` or `1`, or a boolean where the store has one.++## Where policies go++### Windows++Values under `HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PdfCraft` for the whole machine, or+`HKEY_CURRENT_USER\SOFTWARE\Policies\PdfCraft` for one user (Group Policy's User+Configuration). Flags are `REG_DWORD`. When both are set, the one that restricts more wins.++```powershell+New-Item -Path 'HKLM:\SOFTWARE\Policies\PdfCraft' -Force | Out-Null+New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\PdfCraft' -Name 'DisableJavaScript' -PropertyType DWord -Value 1 -Force | Out-Null+```++or, from `cmd` or an RMM component:++```+reg add "HKLM\SOFTWARE\Policies\PdfCraft" /v DisableJavaScript /t REG_DWORD /d 1 /f+```++Only administrators can write either key, `HKCU\SOFTWARE\Policies` included.++### macOS++Managed preferences for the domain `ai.storyteller.pdfcraft`, delivered by a configuration profile.+Most MDMs take the keys as a preference file (plist) for that domain, under a name such as+Application & Custom Settings or Preference file. Flags are booleans. A value someone sets+themselves with `defaults write` is not a policy and is ignored.++```xml+<?xml version="1.0" encoding="UTF-8"?>+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">+<plist version="1.0">+<dict>+    <key>DisableJavaScript</key>+    <true/>+</dict>+</plist>+```++`defaults read "/Library/Managed Preferences/ai.storyteller.pdfcraft"` shows what reached a Mac.++### Linux and the BSDs++A JSON object in `/etc/pdfcraft/policy.json` (`/usr/local/etc/pdfcraft/policy.json` on FreeBSD):++```json+{ "DisableJavaScript": true }+```++Keys PdfCraft doesn't know are ignored, so one file can serve several versions. The Flatpak build+can't see the host's `/etc` and doesn't read this file yet.++## When a policy can't be read++A policy that is there but can't be read, such as a `REG_SZ` where a `REG_DWORD` belongs, a value+other than 0 or 1, a damaged or unreadable `policy.json`, or one larger than 64 KB, counts as+the most restrictive setting: JavaScript stays off. Preferences says the policy couldn't be read,+shows where it is and what was wrong, and the same goes to the log. An administrator who put+something there meant to manage the setting, and falling back to the default would leave+JavaScript on where they believe it is off, with nothing to show it.diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml--- a/parity/acrobat-features.toml+++ b/parity/acrobat-features.toml@@ -5261,7 +5261,7 @@ status = "shipped" commands = ["app.preferences"] tools = ["js_enabled"] evidence = ["crates/js/src/tests.rs::the_sandbox_has_no_host_access", "crates/js/src/tests.rs::errors_and_runaway_scripts_are_contained", "crates/ui-egui/tests/it/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/it/document_links.rs::a_script_asks_first_and_only_once", "crates/ui-egui/tests/it/document_links.rs::a_script_cannot_open_other_kinds_of_address"]-notes = "Preferences \u25b8 JavaScript \u25b8 Enable Acrobat JavaScript (remembered); no host access; loop and recursion limits; app.launchURL asks before opening and only offers http, https and mailto addresses (#90, #91)."+notes = "Preferences \u25b8 JavaScript \u25b8 Enable Acrobat JavaScript (remembered; an administrator's DisableJavaScript policy locks it off, see misc.admin-preferences); no host access; loop and recursion limits; app.launchURL asks before opening and only offers http, https and mailto addresses (#90, #91)."  [[feature]] id = "form.folder-level-js"@@ -10129,11 +10129,11 @@ title = "Admin-deployable preference defaults" area = "N" tier = "P2" milestone = "M14"-status = "planned"-commands = []-tools = []-evidence = []-notes = ""+status = "partial"+commands = ["app.preferences"]+tools = ["js_enabled"]+evidence = ["crates/platform/src/policy.rs::a_policy_file_sets_flags_by_name", "crates/platform/src/policy.rs::a_damaged_policy_file_keeps_everything_locked", "crates/platform/src/policy.rs::the_stricter_of_two_policies_wins", "crates/engine/src/tests.rs::an_administrators_lock_keeps_scripts_off_whatever_the_preference", "crates/engine/src/tests.rs::an_administrators_lock_holds_on_the_background_open_path", "crates/ui-egui/tests/it/javascript.rs::an_administrators_policy_locks_the_preference_off", "crates/automation/tests/it/automation.rs::an_administrators_policy_keeps_javascript_off"]+notes = "Locked policies (Acrobat's FeatureLockDown): DisableJavaScript only (#1846), read at launch from HKLM/HKCU\\SOFTWARE\\Policies\\PdfCraft (Windows), managed preferences for ai.storyteller.pdfcraft (macOS) or /etc/pdfcraft/policy.json (Linux; /usr/local/etc on FreeBSD); an unreadable policy keeps JavaScript off. Missing: other keys (update check, mcp and --control, crash recovery, self-signed IDs), defaults the user may change, ADMX templates, the Flatpak build (no host /etc)."  [[feature]] id = "misc.localization"