feat(content-translator): add incremental richText translation#154
feat(content-translator): add incremental richText translation#154jhb-dev wants to merge 4 commits into
Conversation
|
| if (isUnsafeKey(key)) { | ||
| return | ||
| } | ||
| target[key] = value |
What
Adds a third translation action — "Translate new & changed content" (incremental mode) — alongside the existing "Translate all fields" and "Translate only empty fields".
For lexical
richText, incremental mode diffs the source against the existing translation at the paragraph / block level instead of skipping the whole field (empty-only) or retranslating everything (translate-all):Other field types behave like "translate only empty fields" in incremental mode.
How
Paragraph identity is content-addressed: a hash of the source text (
srcHash) and of the machine output (outHash) are stored inline on the translated node via Lexical's NodeState slot —"$": { "translator-plugin": { "srcHash": …, "outHash": … } }. AsrcHash → targetNodejoin makes the diff robust to insert/delete/reorder/edit, which positional matching cannot survive. Hashes are stamped on every translate so subsequent incremental runs have identity to join on.The boolean
emptyOnlyplumbing was replaced with an explicitmode: 'all' | 'empty' | 'incremental'enum end to end (types → operation → endpoint → traverseFields → client/provider/modal).De-risking
The inline-storage assumption is proven by a committed regression test: a node carrying
$round-trips through a headless editor built from Payload's default lexical config with the slot intact. If a future@payloadcms/richtext-lexicaldrops it, that test fails and the documented sidecar fallback applies.Tests
New behavior-named integration tests cover every row of the classification table (append, middle-insert, edit, reuse, skip+flag conflict, delete, empty-target) plus the NodeState round-trip. Existing traverseFields tests updated for the mode enum. All 22 pass; lint + typecheck clean.
Dev app
The
homepage is seeded with multi-paragraph localized richText so the flow is clickable: translate-all into German, edit/insert/delete an English paragraph, then "Translate new & changed content". Local dev DB reseeded.Docs
README gains a "Translation modes" section; CHANGELOG has an Unreleased entry; the implemented plan (
plans/001-…) is deleted.