* M15: Polish interface, and Polish spelling, grammar and hyphenation
Interface
- Polish is the tenth interface language ("Polski", pl.tsv, 1,045 entries);
pl, pl-PL and pl_PL.UTF-8 system locales select it automatically.
- Right-click menu labels now go through translation in every language.
- Review > Language is a menu (English US/UK, Polski, Do not check spelling
or grammar); the status bar shows the language at the caret.
- With a Polish interface, new blank documents and opened .txt/.md files
default to pl-PL.
Proofing
- Each run is checked in its own language (w:lang, resolved through
styles): squiggles, F7, review.issues, suggestions and hyphenation.
Text in languages without proofing tools is no longer flagged as English.
- Polish spelling from the SJP.PL Hunspell dictionary (Apache-2.0 option),
read by a new Rust affix engine (crates/proof/src/affix.rs); 875 KB asset,
~4.5M word forms. Regenerate with the pl_dictionary example.
- Polish suggestions: case, missing diacritics, rz/ż, ó/u, ch/h confusions,
missing spaces (napewno -> na pewno), edit distance.
- Conservative Polish grammar rules: repeated words, spacing, doubled
punctuation, sentence capitals, missing comma before że/który/ponieważ/
aby/żeby/jeśli…, we/ze forms.
- Polish hyphenation with the hyph-utf8 hyph-pl.tex patterns (MIT option);
pattern keys widened to 20 letters, TeX exceptions parsed; the heuristic
no longer treats ł, ż, ś… as vowels.
review.language validates and normalizes tags, can set noProof alone and
the document default; format.state reports lang and noProof.
Tests for every part, docs and ATTRIBUTION.md/NOTICE updated;
cargo xtask ci passes (fmt, clippy, test, assets, layers, wasm).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Layout: rustfmt the test module order left unformatted by #391
cargo fmt --check failed on main after #391 added mod tests_grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #346: read Polish spelling from the installed Hunspell dictionary
- Polish spelling uses the Hunspell pl_PL dictionary installed on the
system (OS packages, Homebrew, LibreOffice, ~/.local/share/hunspell, or
WORDCRAFT_DICTIONARIES) instead of a bundled 870 KB SJP.PL file;
without one, Polish text gets no spelling marks, like Word without the
language's proofing tools. .aff/.dic are size-capped and decoded from
UTF-8 or ISO 8859-2 (crates/proof/src/hunspell.rs).
- The dictionary can be replaced at runtime (install_dictionary) or
re-searched (reload_dictionary); an empty one accepts every word.
- Tests use small synthetic dictionaries written for them, so they don't
depend on what the machine has installed.
- The bundled pl.dic, its ATTRIBUTION/NOTICE entries and the generator
example are removed; docs describe the installed dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M15: Polish catalog covers the interface text merged from main
The merge of main (98456ac) brought ~575 new interface strings (charts,
SmartArt, content controls, Developer tab, AutoCorrect, outline view,
protection, compare, print, accessibility, references and Language/
Hyphenation dialogs, Draw additions). pl.tsv lacked them, so
polish_covers_every_key_of_every_other_catalog and
every_tab_and_command_is_translated failed. All are now translated.
cargo xtask ci: all 6 steps pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
5.7 KiB
Control protocol
Start the app with a control port: wordcraft --control 7981 (or WORDCRAFT_CONTROL_PORT=7981).
It listens on 127.0.0.1 only. Send one JSON object per line, with the window's key (see
Keys); each gets one reply line:
{"id": 1, "key": "…", "method": "engine.execute", "params": {"command": "text.insert", "params": {"text": "Hello"}}}
{"id": 1, "ok": true, "result": {"anchor": {...}, "focus": {...}}}
Errors come back as {"id": …, "ok": false, "error": "message"}. A failed command leaves the
document unchanged. A line that isn't valid JSON gets one error reply, then the server closes the
connection (so an HTTP request sent to the port can't run a command); reconnect to continue.
Keys
Any local program (and possibly a web page) can reach a loopback port, so every request needs the
window's key. Once the port is bound, the app writes a random key (256 bits, as hex) to
<settings>/control-key.<instance> and removes the file when the window closes; a new start
writes a new key. <settings> is the folder that holds the preferences: on Linux
$XDG_CONFIG_HOME/wordcraft (by default ~/.config/wordcraft), on macOS
~/Library/Application Support/WordCraft, on Windows %APPDATA%\WordCraft. <instance> is the
Flatpak instance id inside Flatpak, and local-<port> otherwise. On Unix the file has mode 0600.
cat ~/.config/wordcraft/control-key.local-7981 # Linux, outside Flatpak, port 7981
- Send the key as top-level
"key"with every request, or once per connection with{"method": "auth", "key": "…"}(reply{"ok": true, "result": {}}); later requests on that connection may leavekeyout. Onlyauthreadsparams.key; for other methodsparams.keyis an ordinary parameter (ui.key {"key": "Enter"}presses Enter). - The key is checked on every request. A missing or wrong key gets
{"ok": false, "error": "unauthorized"}, and the app closes the connection. - A line over 1 MiB gets
{"ok": false, "error": "line too long"}, and the app closes the connection. - A connection that sends no authorised request within 30 s of being accepted is closed. That is one limit: blank lines and bad JSON do not extend it. After an authorised request the connection may stay idle.
wordcraft-cli mcp --connect takes the key from WORDCRAFT_CONTROL_KEY, or else from the key
file for the port it connects to, and reads it again on every new connection (the app may have
restarted). It sends the key only to loopback addresses (127.0.0.0/8, ::1, localhost). For
any other host it sends no key, says so, and the app refuses its requests: to drive an app on
another machine, forward its port to 127.0.0.1 (for example ssh -L 7981:127.0.0.1:7981 host)
and set WORDCRAFT_CONTROL_KEY to that app's key.
Methods
| Method | Params | Result |
|---|---|---|
engine.execute |
command, params |
the command's result (any id from engine.commands, including UI commands ui.tab, ui.dialog, ui.backstage, ui.zotero.* (see docs/zotero.md), ui.language (`{"value": "auto" |
engine.commands |
— | every command: id, label, location, shortcut, params, enabled |
document.inspect |
text? |
blocks (text, style, runs, lists, tables), parts, sections, selection, pages |
ui.inspect |
— | UI state, view, dialog, window size, page rects on screen, caret, comment balloons (balloons: id + screen rect; balloon: the selected one), perf |
ui.click |
x, y, button?, count?, shift?, cmd?, alt? |
real pointer input at window coordinates |
ui.clickText |
page (0-based), x, y (points from the page's top-left), count? |
click inside a page |
ui.move / ui.drag |
x,y / x,y,toX,toY,steps? |
pointer move / drag |
ui.press / ui.release |
x,y, modifiers |
primary button down / up there (with ui.move between: a drag you can screenshot halfway) |
ui.key |
key (B, Enter, Left…), shift?, alt?, cmd? |
key press through egui (shortcuts apply) |
ui.text |
text |
typed text through egui |
ui.screenshot |
path? |
PNG of the window: written to path, or without one returned as base64 png in the reply |
ui.render |
path?, page? (1-based), scale? |
PNG of a page, rendered by the engine: written to path, or without one returned as base64 png in the reply (never a fixed name in the shared temporary folder) |
ui.parity |
— | feature-catalog parity |
ui.resize / ui.focus / app.quit |
window control |
Any other method name is treated as a command id: {"method": "format.bold"} works.
Positions are {"story": "body" | {"part": n}, "path": [block, row, cell, block, …], "off": byteOffset}.