*/ public const RICH = ['body', 'description', 'decisions', 'postmortem']; /** * Never translated here: an address is not a translation, and changing it * has its own consequences on the record's own form. * * @var array */ protected const SKIPPED = ['slug', 'featured_image_alt', 'cover_image_alt']; protected string $view = 'filament.pages.translation-assistant'; protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedLanguage; protected static ?int $navigationSort = 7; #[Url] public ?string $type = null; #[Url] public ?string $record = null; #[Url] public ?string $locale = null; public ?array $data = []; public static function canAccess(): bool { return Auth::user()?->can('panel.access') ?? false; } public static function getNavigationGroup(): string|UnitEnum|null { return __('panel.groups.content'); } public static function getNavigationLabel(): string { return __('panel.translate.title'); } public static function urlFor(Model $record, ?string $locale = null): string { $type = array_search($record::class, PendingTranslations::TYPES, true); return self::getUrl(array_filter(['type' => $type, 'record' => $record->getKey(), 'locale' => $locale])); } public function getTitle(): string|Htmlable { $record = $this->target(); return $record ? __('panel.translate.title_for', ['title' => ContentTypes::title($record), 'language' => $this->languageName($this->locale)]) : __('panel.translate.title'); } public function mount(): void { $record = $this->target(); if ($record === null) { return; } abort_unless(Auth::user()->can('update', $record), 403); $this->form->fill(collect($this->fields($record)) ->mapWithKeys(fn (string $field) => [$field => $record->getTranslation($field, $this->locale, false)]) ->all()); } /** * The record being translated, when there is one and the language is one * to translate into. */ public function target(): ?Model { $model = PendingTranslations::TYPES[$this->type] ?? null; $locales = array_keys((array) config('locales.supported')); if ($model === null || $this->record === null || ! in_array($this->locale, $locales, true) || $this->locale === config('locales.default')) { return null; } return $model::query()->find($this->record); } /** * @return Collection> */ public function pending(): Collection { return PendingTranslations::find(Auth::user()); } /** * @return array */ public function targetLocales(): array { return array_values(array_diff(array_keys((array) config('locales.supported')), [config('locales.default')])); } public function languageName(?string $locale): string { return (string) config("locales.supported.{$locale}.native", $locale); } public function form(Schema $schema): Schema { $record = $this->target(); if ($record === null) { return $schema->statePath('data')->components([]); } $source = (string) config('locales.default'); $stale = TranslationFreshness::staleFields($record, $this->locale); return $schema ->statePath('data') ->components(collect($this->fields($record))->map(function (string $field) use ($record, $source, $stale) { $original = (string) $record->getTranslation($field, $source, false); $rich = in_array($field, self::RICH, true); $label = __("panel.translate.fields.{$field}"); $input = match (true) { $rich => RichEditor::make($field)->hiddenLabel(), mb_strlen($original) > 120 => Textarea::make($field)->hiddenLabel()->rows(4), default => TextInput::make($field)->hiddenLabel(), }; $diff = isset($stale[$field]) ? TextDiff::html($stale[$field]['was'], $original) : null; return Section::make($label) ->description(isset($stale[$field]) ? __('panel.translate.stale_since', ['date' => $stale[$field]['since']->isoFormat('LL')]) : null) ->icon(isset($stale[$field]) ? 'heroicon-o-exclamation-triangle' : null) ->iconColor('warning') ->compact() ->schema(array_filter([ $diff ? Text::make(new HtmlString('
'.e(__('panel.translate.changed')).' '.$diff.'
')) : null, Grid::make(['default' => 1, 'lg' => 2])->schema([ Text::make(new HtmlString( '
'.e($this->languageName($source)).'' .($rich ? $original : nl2br(e($original))).'
' )), $input, ]), ])); })->values()->all()); } protected function getHeaderActions(): array { return $this->target() === null ? [] : [ Action::make('back') ->label(__('panel.translate.back')) ->color('gray') ->url(self::getUrl()), ]; } public function save(): void { $record = $this->target(); abort_unless($record !== null && Auth::user()->can('update', $record), 403); foreach ($this->form->getState() as $field => $value) { if (in_array($field, $this->fields($record), true)) { $record->setTranslation($field, $this->locale, (string) ($value ?? '')); } } $record->save(); Notification::make()->title(__('panel.translate.saved'))->success()->send(); } /** * The fields worth translating on this record: translatable, not an * address, and with something in the original to translate. * * @return array */ protected function fields(Model $record): array { $source = (string) config('locales.default'); return array_values(array_filter( array_diff($record->getTranslatableAttributes(), self::SKIPPED), fn (string $field) => filled($record->getTranslation($field, $source, false)), )); } }