Add a translation assistant to the panel

Each field's original sits beside the field to write, and a translation that
fell behind shows what the original said when it was translated and what it
says now, word by word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 10:53:31 +02:00
parent 76d022053b
commit 6fc600e6a1
16 changed files with 837 additions and 61 deletions

View File

@@ -71,6 +71,69 @@ final class TranslationFreshness
/**
* @return array<string, CarbonInterface>
*/
/**
* Field by field, for one language: which fields the original changed
* after this language was last written, since when, and what the
* original said back then — so a translator can see what to redo rather
* than rereading the whole text.
*
* @return array<string, array{since: CarbonInterface, was: ?string}>
*/
public static function staleFields(Model $record, string $locale): array
{
$source = (string) config('locales.default');
$fields = self::fields($record);
$touched = self::lastTouched($record, $fields);
$stale = [];
foreach ($fields as $field) {
if ($locale === $source || blank($record->getTranslation($field, $locale, false))) {
continue;
}
$sourceAt = $touched[$field][$source] ?? null;
$translatedAt = $touched[$field][$locale] ?? $record->created_at;
if ($sourceAt !== null && $translatedAt !== null && $sourceAt->gt($translatedAt)) {
$stale[$field] = [
'since' => $sourceAt,
'was' => self::sourceAsOf($record, $field, $translatedAt),
];
}
}
return $stale;
}
/**
* The original's text in a field as it stood at a moment, from the
* history: the last value written at or before it, or the value the first
* later change replaced.
*/
protected static function sourceAsOf(Model $record, string $field, CarbonInterface $at): ?string
{
$source = (string) config('locales.default');
$value = null;
$found = false;
foreach ($record->revisions()->reorder()->orderBy('id')->get() as $revision) {
$change = $revision->changeset[$field] ?? null;
if (! is_array($change)) {
continue;
}
if ($revision->created_at->lte($at)) {
$value = ((array) ($change['new'] ?? []))[$source] ?? null;
$found = true;
} elseif (! $found) {
return ((array) ($change['old'] ?? []))[$source] ?? null;
}
}
return $found ? $value : $record->getTranslation($field, $source, false);
}
protected static function compute(Model $record): array
{
$source = (string) config('locales.default');