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>
82 lines
2.7 KiB
PHP
82 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\Call;
|
|
use App\Models\Page;
|
|
use App\Models\Project;
|
|
use App\Models\Service;
|
|
use App\Models\User;
|
|
use Carbon\CarbonInterface;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Collection;
|
|
|
|
/**
|
|
* Records with a language still to write (`missing`) or left behind by the
|
|
* one it was translated from (`stale`, with since when) — for the panel's
|
|
* translation assistant and the API alike.
|
|
*/
|
|
final class PendingTranslations
|
|
{
|
|
/**
|
|
* @var array<string, class-string<Model>>
|
|
*/
|
|
public const TYPES = [
|
|
'pages' => Page::class,
|
|
'projects' => Project::class,
|
|
'services' => Service::class,
|
|
'calls' => Call::class,
|
|
];
|
|
|
|
/**
|
|
* @return Collection<int, array{type: string, id: int|string, record: Model, title: string, missing: array<int, string>, stale: array<string, CarbonInterface>}>
|
|
*/
|
|
public static function find(User $user, ?string $locale = null, ?string $type = null, bool $publishedOnly = false): Collection
|
|
{
|
|
$locales = array_keys((array) config('locales.supported'));
|
|
$source = (string) config('locales.default');
|
|
$items = collect();
|
|
|
|
foreach (self::TYPES as $name => $model) {
|
|
if (($type !== null && $type !== $name) || $user->cannot('viewAny', $model)) {
|
|
continue;
|
|
}
|
|
|
|
$model::query()
|
|
->when($publishedOnly, fn ($q) => $model === Call::class ? $q->open() : $q->published())
|
|
->with('revisions')
|
|
->get()
|
|
->each(function (Model $record) use ($locales, $source, $locale, $name, $items): void {
|
|
$missing = array_values(array_filter(
|
|
$locales,
|
|
fn (string $l) => $l !== $source && blank($record->getTranslation('title', $l, false)),
|
|
));
|
|
|
|
$stale = TranslationFreshness::for($record);
|
|
|
|
if ($locale !== null) {
|
|
$missing = array_values(array_intersect($missing, [$locale]));
|
|
$stale = array_intersect_key($stale, [$locale => true]);
|
|
}
|
|
|
|
if ($missing === [] && $stale === []) {
|
|
return;
|
|
}
|
|
|
|
$items->push([
|
|
'type' => $name,
|
|
'id' => $record->getKey(),
|
|
'record' => $record,
|
|
'title' => ContentTypes::title($record),
|
|
'missing' => $missing,
|
|
'stale' => $stale,
|
|
]);
|
|
});
|
|
}
|
|
|
|
return $items->values();
|
|
}
|
|
}
|