Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
91 lines
2.9 KiB
PHP
91 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Support;
|
|
|
|
use App\Support\TranslationFreshness;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
/**
|
|
* Shows at a glance which languages a record has been translated into.
|
|
*
|
|
* Each supported locale renders as a badge — solid when a translation exists,
|
|
* muted when it is still missing — so an editor can spot untranslated content
|
|
* without opening every record.
|
|
*
|
|
* A third state matters as much as the first two and is invisible everywhere
|
|
* else: a translation that exists and has fallen behind the language it was
|
|
* made from. Nothing else in the panel would ever mention it, because by every
|
|
* other measure that record is finished.
|
|
*/
|
|
class TranslationStatusColumn extends TextColumn
|
|
{
|
|
/**
|
|
* The translatable attribute whose presence stands in for "translated".
|
|
* Defaults to the model's first translatable attribute.
|
|
*/
|
|
protected ?string $trackedAttribute = null;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->badge();
|
|
$this->sortable(false);
|
|
$this->searchable(false);
|
|
|
|
$this->state(function (Model $record): array {
|
|
$attribute = $this->resolveTrackedAttribute($record);
|
|
$translations = $record->getTranslations($attribute);
|
|
$stale = TranslationFreshness::for($record);
|
|
|
|
$states = [];
|
|
|
|
foreach (array_keys(config('locales.supported')) as $locale) {
|
|
if (blank($translations[$locale] ?? null)) {
|
|
$states[] = strtolower($locale);
|
|
|
|
continue;
|
|
}
|
|
|
|
// Translated, but the language it was translated from has
|
|
// moved on since. The badge has to say something other than
|
|
// "done", because as far as everything else is concerned this
|
|
// record is finished.
|
|
$states[] = isset($stale[$locale])
|
|
? strtoupper($locale).'!'
|
|
: strtoupper($locale);
|
|
}
|
|
|
|
return $states;
|
|
});
|
|
|
|
/*
|
|
* Uppercase = translated, lowercase = missing, and a "!" for a
|
|
* translation the source has since moved past. Colour carries the same
|
|
* three states for anyone who does not notice the casing.
|
|
*/
|
|
$this->color(fn (string $state): string => match (true) {
|
|
str_ends_with($state, '!') => 'warning',
|
|
$state === strtoupper($state) => 'success',
|
|
default => 'gray',
|
|
});
|
|
}
|
|
|
|
public function trackedAttribute(string $attribute): static
|
|
{
|
|
$this->trackedAttribute = $attribute;
|
|
|
|
return $this;
|
|
}
|
|
|
|
protected function resolveTrackedAttribute(Model $record): string
|
|
{
|
|
return $this->trackedAttribute
|
|
?? $record->getTranslatableAttributes()[0]
|
|
?? 'title';
|
|
}
|
|
}
|