Skip to content

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

Description

@SearheiParkhamchuk

Summary in one line

The admin shows "this translation is out of date", and strategy: "skip_existing" then refuses to re-translate that very field — the two features answer the same question differently.

What happens

  1. Translate de. Provenance stores a fingerprint of the source's translatable content.
  2. Edit the English source.
  3. The admin's staleness indicator ([translator] Detect and surface out-of-sync (stale) translations #50) lights up: the translation is out of date.
  4. Run a translation with strategy: "skip_existing", which is the natural choice for "refresh what needs refreshing".

Nothing is re-translated. The endpoint returns 200.

Why

skip_existing has exactly one criterion — is the target field empty:

// src/core/translation-pipeline/strategies/SkipExisting.strategy.ts:11-14
shouldTranslate(ctx: StrategyContext): boolean {
  if (isEmpty(ctx.sourceValue)) return false;
  return this.isEmptyValue(ctx.targetValue);
}

Staleness is computed by isRecordStale (src/core/domain/provenance/staleness.ts), and its only consumers are the staleness HTTP routes that feed the admin indicator. The pipeline never asks.

So "already translated" means "not empty" — with no notion of current, and no notion of reviewed: a machine translation nobody read, a placeholder, or a half-typed word all count as done.

Why the obvious fix is wrong

Making SkipExistingStrategy call isRecordStale looks like a few lines. It is a trap, because the two work at different granularities:

  • Staleness is per document-locale. computeSourceFingerprint(doc, schema) is one sha256 over the whole document's translatable content, and TranslationProvenanceRecord stores one sourceFingerprint per (collectionSlug, documentId, targetLocale).
  • The strategy decides per field.

So the only thing that integration can express is a document-wide switch: if the document is stale, re-translate every non-empty field. That destroys work:

A document has title and body. A reviewer corrects the German title. Somebody then edits the English body. The document is now stale, so the run re-translates the title too — overwriting a correction whose source never changed.

That is the same failure as #116, reached from a different direction and firing on every run rather than only on publish.

What a correct fix needs

Per-field provenance, so the strategy can ask "did this leaf's source change":

  1. compute a fingerprint per translatable leaf — cheap, the content projector already walks them;
  2. store them — this changes the provenance collection's shape and needs a migration;
  3. give StrategyContext a third input (the source value this leaf was translated from) — a contract change in core;
  4. a strategy or mode that uses it, plus tests.

A cheaper option worth considering instead

Leave skip_existing alone and add a separate, explicitly chosen strategy — "re-translate what is stale" — that re-translates everything when the document is stale. Coarse, and it has the same overwrite behaviour as above, but the editor picks it knowingly rather than being surprised by it. Much smaller, and it removes the contradiction between the indicator and the strategy without a storage migration.

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions