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'; } }