* build(i18n): embed the craft-i18n catalogs from a pinned checkout
- `craft-i18n.rev` pins storytold/craft-i18n at e45ddf7 (the PdfCraft cutover sync of
pdfcraft@ecdd1e6); `.gitattributes` keeps it LF.
- The `craft-i18n` crate (craft-libs 787a7a0, PdfCraft's existing pin) is a direct dependency
of the UI crate; it was already in the tree through craft-ui.
- `crates/ui-egui/build.rs` writes `CRAFT_I18N` from `CRAFT_I18N_DIR`: one (locale, common.tsv,
pdfcraft.tsv) entry per locale with a `pdfcraft.tsv`, paths written with `{:?}` and never
canonicalized. Unset it is empty (English only); `CRAFT_I18N_REQUIRED=1` makes a missing or
invalid checkout an error.
- Both flatpak manifests name the craft-i18n commit and `CRAFT_I18N_DIR`; a test keeps both
commits equal to the trimmed pin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(i18n): look strings up through craft-i18n behind a thin Lang wrapper
`i18n.rs` keeps PdfCraft's API (tr, tr_ctx, tr_id, trn, key_name, fmt,
Lang, LANGUAGES, detection) and hands lookups to the craft-i18n crate:
`get` for key names, `trn_plain` for plurals (no isolates, no digit
grouping), PdfCraft's own `fmt` (no FSI isolates). `@id` rows and the
"Undo {action}" template are read only where PdfCraft had them, so menus
and history labels read exactly as before.
Detection is unchanged (LC_ALL/LC_MESSAGES/LANG, macOS defaults,
sys_locale); the crate's match_tag would drop nb for no/nn, so PdfCraft's
own tag mapping stays, with one parity test per path.
Completeness tests become reports; tests that need catalogs skip with a
note when the build has none. Font coverage tests read the embedded
catalogs (shared and PdfCraft rows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(i18n): remove the in-repo catalogs; craft-i18n holds them
The 34 `crates/ui-egui/src/i18n/*.tsv` catalogs were migrated into
craft-i18n at pdfcraft@ecdd1e6 and are now embedded from the pinned
checkout. ATTRIBUTION.toml drops their 34 entries for one craft-i18n
`[[build_input]]` (craft-i18n's ATTRIBUTION.md credits the translators);
AGENTS.md and docs/localization.md describe the new system and how to
add a language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(i18n): skip catalog tests without craft-i18n; drop the catalog hash exemption
Tests that switch to a non-English language skip with a note when the
build embeds no catalogs (CRAFT_I18N_DIR unset), so the English-only
build tests green. `skip_without_catalogs` moves to the integration
test support module.
With no catalogs left in the repository, `cargo xtask assets` no longer
exempts `crates/ui-egui/src/i18n/*.tsv` from hashing: every `[[asset]]`
carries a sha256 again, and a `.tsv` added back would need one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* build(i18n): watch the locales directory before reading it
A CRAFT_I18N_DIR that doesn't exist yet now reruns the build script once it appears (review).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* build(i18n): pin craft-i18n dfbe955, the merge of storytold/craft-i18n#129
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(i18n): skip an @id row that changes the label's placeholders or ellipsis
craft-i18n checks placeholders and the trailing ellipsis on every row whose
source is English text, but not on @id rows, whose source is the command id.
The in-repo catalog test used to fail on such a row; since the cutover it only
prints it, while tr_id and menu_label still showed it. Both now use PdfCraft's
own @id row only when it keeps the label's shape (same_shape, the rule
craft-i18n applies elsewhere), and otherwise show the label's translation, as
bundled_catalogs_are_consistent says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(i18n): reject an @id row with an open brace; list unused @id rows in the gaps report
same_shape skipped a `{` with no `}`, so an @id row such as "Öffnen {name…"
for "Open…" was still shown. It now counts as a changed shape.
bundled_catalogs_are_consistent printed the @id rows PdfCraft doesn't use, but
CI captures test output, so nobody saw them. translation_gaps_report, which
`cargo xtask i18n-report` runs with --nocapture, now lists them
(unused_id_rows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(i18n): reject an @id row with a `}` that closes no placeholder
same_shape counted only the `}` that close a `{`, so "Öffnen name}…" for
"Open…" or "{name}} öffnen" for "Open {name}" still passed. A row whose `}`
count differs from its placeholders now counts as a changed shape.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(i18n): no braces in the assert messages of the brace test
assert! reads its message as a format string, so "a `}` without its `{`"
did not compile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
The Double code table's attribution pointed its licence file at the
table's own source. `cargo xtask license-check` requires every asset's
licence file to be a LICENSES.toml document, so the gate failed.
Add prc-rs's LICENSE at the same commit as `licenses/prc-rs-LICENSE.txt`,
fetched unchanged, with its origin and hash in LICENSES.toml, and point
the attribution entry at it. The MIT notice stays in the table's header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
Every PRC Double decodes through a fixed 2077-row prefix-code table
(working draft 11.17.2). The rows come from prc-rs src/double.rs at
287b671bb5b31b05efb5ec1b98f7c359b9e4dd7d under its MIT licence, whose
notice stays in the file header; ATTRIBUTION.toml records it.
The table is a complete prefix code, so the empty-table stub test is
replaced by a truncation test. The exponent lookup masks the 11 exponent
bits, because the last exponent row's upper word is 0x7ff80000.
Revert proof: with the table emptied, eight Double tests fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
* Make PdfCraft's app colour red
The icon tile, the Windows PDF file icon's badge and lines, the DMG background and the README
badges move from green #12a58a to crimson #b70228 (darker shade #8a011e, fold #f3cfd6).
Every PNG, .ico and .icns is re-rendered from the edited master SVGs with packaging/icons.sh
and resvg 0.47; the .icns is written with Pillow because iconutil is macOS-only, with
iconutil's size set. The DMG background is re-rendered with generate.py; its .DS_Store is
unchanged. ATTRIBUTION.toml hashes updated; cargo xtask assets passes.
* Fix OCR model attribution hashes
* Record successful PdfCraft web application check
* E21: bound startup requests and validate font and license inputs
* E21: separate pending control completion and gesture regression phases
* M-egui037: enforce control admission and per-family font notices
* M-egui037: qualify capture fixes and document migration behavior
* egui-037: identify current and historical qualification results
* i18n(fa): add Persian interface translation catalog
Add crates/ui-egui/src/i18n/fa.tsv covering the current 2,776-key union, register fa (right-to-left, one/other plurals) in i18n and the date languages, and credit the catalog in ATTRIBUTION.
* i18n(fa): fix missing ZWNJ in compound suffixes
Join participles (شده/نشده) and productive suffixes (سازی, گذاری, گیری, بندی, پذیری, کننده, ...) and compound prefixes (هم, غیر, پیش, پس, چند, تک, باز) with U+200C, plus reviewer wording fixes.
* fix(i18n): Persian counts 0 as singular (CLDR fa) and writes weekday names with ZWNJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- crates/render/src/inspect.rs: this branch's reply-linking methods and main's NoRotate
rectangles (#3016) both go in the Annotation impl; the annotation reader takes this
branch's loop with main's no_rotate field.
- crates/automation/src/comments.rs: this branch's HashMap import, main's import list.
- ATTRIBUTION.toml: each catalogue's source note keeps main's secured-document clause and
this branch's reply-count clause; ATTRIBUTION.md regenerated (cargo xtask assets --write).
- The 33 catalogues: main's new rows, then this branch's reply-count row.
main's #3016 tests build Annotation field by field; they now set parent and grouped.
main's #4284 stopped hashing translation catalogues in ATTRIBUTION.toml; the
conflict took main's side (no sha256 lines) and kept this branch's source notes.
Conflicts:
- ATTRIBUTION.toml: fi.tsv source: main's text plus the PR's added sentence; the PR's sha256 line dropped (translation catalogs carry none on main).
- ATTRIBUTION.md: main's rows, fi row updated to the same source text.
* Assets: translation catalogs aren't hashed
The 33 UI translation catalogs are project files under the project
licence, and git history records their provenance. Their ATTRIBUTION.toml
entries keep title, author, source and licence but carry no sha256, so a
PR that changes UI text no longer has to edit ATTRIBUTION.toml.
cargo xtask assets accepts a kind = "translation" entry for a .tsv under
crates/ui-egui/src/i18n/ without a hash, rejects one that has a hash, and
still requires a hash for every other entry.
* Assets: a leftover hash on a translation catalog is a note, not an error
* Allow OFL typefaces whoever designed them (AGENTS.md §1.1)
Noto CJK and Source Han from their open-source releases are allowed like any
other OFL font. The fonts build no longer skips them, the asset check accepts
an OFL font credited to Adobe, and the docs describe the Chinese faces that
result. The ban on Adobe icons, artwork, UI graphics and the fonts Adobe
products install stays.
* Leave out whole CJK faces other than the Japanese ones for size
A Chinese or Korean face is embedded whole only under 4 MiB, so the 16.4 MB
Noto Sans CJK SC in craft-fonts stays out of the binary and the embedded
total against the pinned craft-fonts is unchanged (25,204,828 bytes).
* docs: replacement boxes for Chinese without craft-fonts only on the web or without an installed Chinese face
* xtask assets: the OFL font exemption needs at least one licence ID
* fonts build: count a CJK script tag with a region (Hant-HK) as CJK, shared with the embedding test
* AGENTS.md: the asset gate exempts OFL fonts from the Adobe-author check
* fonts build: count a Hangul-only face (Hang) as CJK for the size rule
* fonts: the CJK embedding test checks the size rule, sharing its constants with build.rs
* docs: the missing Hans face is a property of the pinned craft-fonts, not of every build
The header keeps Acrobat's count of comments and adds the replies in
small text below it ("16 replies"), left out when there are none and
counting the whole document rather than the current search. Status
changes and checkmarks are not replies. The plural label is in all 33
catalogues with each language's plural forms; ATTRIBUTION.toml records
the new hashes and that the label is an AI-assisted translation not yet
reviewed by native speakers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KvugvPrjZtPVudhh7cMa54
* G14: Romanian: translate the 65 catalog rows still in English
The Romanian catalog landed in main through the integration branch
(bf3a199f, PR #2505); the keys main gained since were filled with English.
This translates them: calculation order, the zoom box, rich-text formatting,
print options, Pages thumbnail size, Reshape comment, the CMS signed-attribute
and short-RSA-key warnings, and the page-numbering Start and page Subset
labels. Romanian translations main already has (the text field table) are
kept as they are. ATTRIBUTION.toml carries the new SHA-256.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk
* G14: Romanian: quote „data” in the CMS warning; Subset reads Selecție
Review of #4016: the content type "data" is quoted so it isn't read as "the
date", and the page Subset label and the booklet subset label both read
"Selecție" instead of "Filtru" / the anglicism "Subset broșură".
ATTRIBUTION.toml carries the new SHA-256.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The notice that a document is secured came back on every open; Dismiss
lasted only for that view. Its new "Don't show again" turns it off in
every document, kept in the settings as show_secured_notice and turned
back on in Preferences (or --secured-notice). Only the notice changes:
the restrictions, the encryption and Document Properties > Security stay
as they are. The two labels are translated in all 33 catalogues, whose
ATTRIBUTION hashes and provenance notes are refreshed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the English placeholders added during the #2210 integration
(calculation order, text formatting, print, thumbnails, layers, comment
reshape and signature checks, plus the two signature wordings from
3e180944) with Finnish, and updates the fi.tsv sha256 and the entry
count in docs/localization.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Build each crate's integration tests as one binary
Every tests/*.rs file is its own executable that links the whole crate
graph. Move each crate's test files into tests/it/ as modules of one
tests/it/main.rs, for apps/pdfcraft, apps/pdfcraft-cli, automation, cos,
crypt, engine, render, sign and ui-egui. ui-egui's ui_group binary and
the support modules move into tests/it/ too. Crates with a single test
file keep it. The test executables go from 150 to 52, and every crate
runs the same number of tests as before.
ui-egui perf keeps its own binary (own process): its frame-time budgets
would charge each measurement for the other tests running beside it.
What the move needs:
- include_str!/include_bytes! and #[path] paths gain one "../".
- ui-egui: one GPU lock in tests/it/main.rs replaces the five per-file
ones (content, document_links, editing, fill_sign, view), so one
process creates one wgpu device at a time. The drop-line test in ui.rs
and the screenshot blocks behind PDFCRAFT_*_SHOT(S) and
CRAFT_UI_DOCKING_DIR, which render without holding it, take it too.
- pdfcraft winit_wayland_scroll: the vendored winit source it compiles
names crate::event, so that shim lives in main.rs.
- parity evidence names the new paths.
* Name the one-binary test paths in docs and comments
- READMEs, docs, ATTRIBUTION and source comments name tests/it/<file>.rs.
- The oracle README's example filters with --test it encryption::qpdf_agrees.
- pdfcraft's GPU lock comment points at the UI tests' shared lock in
crates/ui-egui/tests/it/main.rs.
Albanian, Catalan, Finnish, Hindi, Korean, Indonesian and Romanian get all
22 text-field-table strings; Brazilian Portuguese gets the 4 limit messages
its full-union test requires. AI-assisted, marked in ATTRIBUTION.toml.
English fill (the "Untranslated" section of each catalogue), for keys the
coverage tests only need present:
- ca, fi, ro, pt-br: keys other catalogues gained (print, thumbnails,
layers, signature checks, Reshape comment, Remove {standard}
identification)
- sq: UI literals and the visible-signature labels
- hi, ko, id: calculation-order, rich-text and print/thumbnail literals
- pl: signature-check wordings
AI-assisted translation (noted in ATTRIBUTION.toml):
- cs: the four new signature-check wordings and Remove {standard}
identification, since Czech asserts every entry is translated
Lao's file.word_count command id said its own id; it reads as the
plain "Word count…" translation.
#3101's page_gap_labels_are_translated_in_every_catalog asserts that every
catalog translates its two labels. The Finnish, Albanian, Thai, Catalan,
Romanian, Lao and Central Kurdish catalogs from track t12 lack them. The
translations are AI-assisted and noted as such in ATTRIBUTION.toml.
* M14: add Norwegian Bokmål interface and date language
A 2,592-entry `nb.tsv` covering every key the other bundled catalogs have,
written from the meaning of the English labels (clean-room); terminology
reviewed by a native-speaking contributor. Registers `nb` with the English
one/other plural rule, adds Norwegian month and weekday names for Fill &
Sign dates, resolves `no` and `nn` locales to Bokmål until a Nynorsk catalog
exists, attributes the catalog and records it in parity.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the strings main added since the Norwegian catalog was written
69 keys (signature appearance labels, word count, full-screen prompt, print
as image, image crop, redaction mark appearance, …) and the attribution hash.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the picture-menu and label-paper strings in the Norwegian catalog
Seven keys main added (Cut/Redact Image, Create Link…, Recognize Text…,
Turn upside down, the 4 x 6 in label paper) and the attribution hash.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the page-group folding commands in the Norwegian catalog
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: drop the superseded localization summaries left beside the current ones
Concurrent merges on main left older copies of several localization
summaries next to their replacements. Review of #1836 flagged them:
- localization-parity.md: remove the 2026-10-13 status line and the
14-language summary that still listed Norwegian as missing; the
current summary keeps the Italian (and now Norwegian Bokmål)
measurement dates.
- localization.md: keep one catalog-coverage paragraph (now naming
Norwegian Bokmål, up to about 2,700 entries) and one font paragraph.
The font paragraph folds in what only the older copies said: the
installed Chinese face fallback (#826) and Slovenian's Latin faces and
four plural forms.
- roadmap.md: end the M14 Missing list before the Fontconfig note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: arrays sized 30.
ATTRIBUTION.md: both sides' rows; count regenerated after all merges.
docs/localization.md: main's text with the ckb name, code and catalog-list entry added.
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 28.
crates/ui-egui/tests/fonts.rs: both catalog lists.
ATTRIBUTION.md: both sides' rows; count regenerated after all merges.
ROADMAP.md: main's summary with Romanian added (15 more catalogs); both log lines.
docs/localization-parity.md: main's rows plus the ro row; 28 interface languages; both revision rows.
docs/localization.md: main's text with Română, the ro locales and code, and the PR's catalog-list edits.
page_gap_labels_are_translated_in_every_catalog (#3101) requires both labels translated in every catalog, including the three #1976 adds; English filler would fail its assert_ne. AI-assisted translations, noted in ATTRIBUTION.toml.
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 27.
crates/ui-egui/tests/fonts.rs: both catalog lists; Catalan added to main's comment.
ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges.
ROADMAP.md: both log lines.
docs/localization-parity.md: main's rows plus the ca row; 27 interface languages; both revision rows.
docs/localization.md: main's text with Català, the ca locales, code and catalog paragraph added.
Conflict resolutions:
- ATTRIBUTION.toml: main's entries kept; #3101's page-gap provenance note appended to the 22 catalogs it translates (hashes recomputed separately).
- ATTRIBUTION.md: main's side kept; regenerated from ATTRIBUTION.toml separately.
- ROADMAP.md: #3101's two 2026-10-18 progress-log lines placed after the 2026-10-19 entries.
- docs/roadmap.md: both 2026-10-18 status lines kept; M3 row carries both sides' insertions.
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows (vi, fi, sq), arrays sized 25.
crates/ui-egui/tests/fonts.rs: both catalog lists; Albanian added to main's comment.
ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges.
ROADMAP.md: both log lines.
docs/localization-parity.md: main's rows plus the sq row; 25 interface languages; both revision rows.
docs/localization.md: main's text with Shqip, the sq locales and code added.
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: summary names Vietnamese, Brazilian Portuguese and Spanish against the current union; both log lines.
docs/localization-parity.md: main's status line with the Spanish change added; the PR's denominator paragraph naming Brazilian Portuguese too; the PR's lt row plus main's fi row; revision rows newest first.