Translations
Krios tracks translation lifecycle separately from the editorial workflow. Both axes operate per-(entry, locale): one tracks "where in the editorial review is this locale?", the other tracks "where in the translation pipeline is this locale?".
TranslationStatus model
model TranslationStatus {
entryId String
locale String
status String // not-started | in-progress | in-review | approved | published
assigneeId String? // who's translating
sourceLocale String? // which locale this was translated from
sourceVersionNum Int? // the source's version when translation started
isStale Boolean // source has been updated since
notes String?
completedAt DateTime?
}
Distinct from ContentLocaleState.workflowState which handles editorial review.
Stale detection
When the site's default (source) locale is saved, every other locale's TranslationStatus is checked: if its recorded sourceVersionNum is older than the new source version, isStale = true is set. The dashboard surfaces a red badge so the translation team sees the divergence the next time they look.
Status transitions
PUT /api/v1/projects/demo/translations/{entryId}/{locale}
{
"status": "in-progress",
"assigneeId": "ckl_user_…",
"notes": "starting translation"
}
Translators clear the stale flag via:
PUT /api/v1/projects/demo/translations/{entryId}/{locale}
{ "acknowledgeStale": true }
This bumps sourceVersionNum to the current entry version, signaling "I reviewed the source diff and my work is still good".
Dashboard
/admin/projects/{slug}/translations renders a matrix:
- One row per entry
- One column per locale (union of every site's
supportedLocales) - Cell shows the locale's status as a colored badge; the source locale is labeled
·src - Click a cell → dialog to set status / assignee / notes; stale rows show an "Acknowledge stale" button
Filters: title search, site, type, status, stale-only checkbox.
Stat cards across the top: entries + per-status counts.
Copy-from-locale
Seed a translation from another locale:
POST /api/v1/projects/demo/entries/{id}/copy-locale
{ "sourceLocale": "en-US", "targetLocale": "fr-CA" }
Copies localizable field values from source to target on the same entry. Sets the target's TranslationStatus.status to in-progress with sourceLocale and sourceVersionNum recorded so stale detection works.
The entry editor shows a "No content for {locale}. Copy from {source}?" callout when the current locale has zero localizable values and the site's default locale has values.
Per-locale workflow independence
If the content type has a workflow assigned, each locale progresses through the workflow independently — English can be Published while French is In Review. Translation status and workflow state are independent axes.
Manually mark stale
POST /api/v1/projects/demo/translations/{entryId}/{locale}/mark-stale
Useful when the translator finds a problem with the existing translation that wasn't triggered by a source change.
API
GET /api/v1/projects/{slug}/translations # dashboard list
GET /api/v1/projects/{slug}/translations/{entryId} # single-entry detail
PUT /api/v1/projects/{slug}/translations/{entryId}/{locale}
POST /api/v1/projects/{slug}/translations/{entryId}/{locale}/mark-stale
GET /api/v1/projects/{slug}/translations/stats # aggregate counts
Audit: translation.status_updated and translation.marked_stale rows on every change.