Skip to content

feat(translator): refresh a translation whose source moved, not just an empty one - #168

Merged
SearheiParkhamchuk merged 3 commits into
mainfrom
feat/translator-access-control-and-request-scope
Oct 5, 2026
Merged

SearheiParkhamchuk merged 3 commits into
mainfrom
feat/translator-access-control-and-request-scope

Conversation

@SearheiParkhamchuk

Copy link
Copy Markdown
Contributor

Closes #118

The problem

A translation receipt recorded one fingerprint for the whole document, so skip_existing could only ask one question: is the target empty? The admin flagged a locale as out of date, a run was started, and nothing changed.

What changes

The receipt now records one fingerprint per translatable leaf, keyed by the leaf's id-based address, stored as JSON in the same text column. No schema change, no migration.

skip_existing refreshes a leaf whose source moved since it was translated and leaves a sibling nobody edited alone. overwrite is unchanged, and the strategies are not deprecated.

A leaf is recorded three ways, and the third state is load-bearing:

recorded meaning
a hash translated from this text
a marker the run saw it and declined
absent it appeared after the translation

Without the marker, a partial run writes a shorter map, which reads as drift forever — and the skipped leaf could only be added by translating it, which the strategy refuses to do.

Addresses come from the source document rather than the walked data, because the reconciler strips a per-locale row's id. A unit guard now compares both sides' addresses, not just their text.

Accepted price

The receipt cannot tell a human's text from the plugin's. A target somebody rewrote by hand is overwritten once its own source changes. Fingerprinting the target instead would read every leaf as hand-edited after a round-trip through Payload, and refreshes would stop silently. This is pinned by a test, not left to chance.

Public API

TranslationProvenanceRecord no longer carries sourceFingerprint or dismissedFingerprint. How the plugin decides a locale is out of date is its own business — the hash width, the address grammar and the leaf record are mechanism, not contract. A consumer reading the sidecar wants which document, which locale and when; staleness is answered by the endpoint.

This removes two fields from a published type. It ships as a minor while the package is pre-1.0.

The admin button

The button under the "out of date" badge ran overwrite, so it replaced every translated leaf of the locale, including corrections whose source had never moved. It now runs skip_existing.

Verification

  • 1929 unit tests; type-check clean in both the package and the sandbox app; oxlint at its 55-warning baseline with no errors
  • 18 new integration cases over 5 files, none skipped on any adapter
  • Integration: SQLite 178, PostgreSQL 184, MongoDB 177, re-run after a rebuild

Not yet exercised against current main — the branch is behind it.

Not covered

The admin status panel has no automated test of any kind, so the button change above is guarded by review only.

…an empty one

A translation receipt recorded one fingerprint for the whole document, so
`skip_existing` could only ask "is the target empty". The admin flagged a locale
as out of date and a run then changed nothing.

The receipt now records one fingerprint per translatable leaf, keyed by the
leaf's id-based address, in the same text column as JSON. No schema change and
no migration. `skip_existing` refreshes a leaf whose source moved since it was
translated, and leaves a sibling nobody edited alone. `overwrite` is unchanged.

A leaf is recorded three ways: the hash it was translated from, a marker for one
the run saw and declined, or absent when it appeared after the translation. The
marker is what stops a partial run writing a shorter map that reads as drift
forever.

Accepted price: the receipt cannot tell a human's text from the plugin's, so a
target somebody rewrote by hand is overwritten once its own source changes.
Fingerprinting the target instead would read every leaf as hand-edited after a
round-trip through Payload, and refreshes would stop silently.

closes #118
The button under the "out of date" badge ran `overwrite`, so it replaced every
translated leaf of the locale, including ones a reviewer had corrected and whose
source had never moved. It now runs `skip_existing`, which refreshes only the
leaves the receipt shows as stale.
…rd type

How the plugin decides a locale is out of date is its own business: the hash
width, the address grammar and the three-valued leaf record are mechanism, not
contract. A consumer reading the sidecar collection wants which document, which
locale and when it was translated; whether it is stale is answered by the
staleness endpoint.

`TranslationProvenanceRecord` therefore no longer carries `sourceFingerprint` or
`dismissedFingerprint`, and neither the fingerprint type nor its parser is
exported from the package entry. The fingerprints live on an internal
`ProvenanceReceipt` that extends the public record.

This removes two fields from a published type. Shipping it as a minor while the
package is pre-1.0.
@vercel

vercel Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ideal-cms Ready Ready Preview Oct 5, 2026 4:39pm UTC

Request Review

@SearheiParkhamchuk
SearheiParkhamchuk merged commit fea618b into main Oct 5, 2026
2 checks passed
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.15.0 🎉

The release is available on npm package (@latest dist-tag)

Your semantic-release bot 📦🚀

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.2.0 🎉

The release is available on npm package (@latest dist-tag)

Your semantic-release bot 📦🚀

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 2.10.1 🎉

The release is available on npm package (@latest dist-tag)

Your semantic-release bot 📦🚀

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.11.2 🎉

The release is available on npm package (@latest dist-tag)

Your semantic-release bot 📦🚀

This branch was successfully deployed

1 active deployment
Preview — e1707a30 Deployed Oct 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

translator: skip_existing ignores staleness, so the admin indicator and the strategy disagree

1 participant