Astrotomic / laravel-translatable

A Laravel package for multilingual models
https://docs.astrotomic.info/laravel-translatable/
MIT License
1.23k stars 155 forks source link

Add support Filament and simple solution #306

Closed Solunsky closed 1 year ago

Solunsky commented 1 year ago

I wrote a simple solution how to integrate in to Laravel Filemant 2.x I edit this issue integration, becouse found a better way.

  1. Create trait Translatable
    
    namespace App\Filament\Traits;

use Illuminate\Database\Eloquent\Builder;

trait Translatable { // This override get translations fields protected function fillForm(): void { $this->callHook('beforeFill');

    $data = $this->getRecord()->attributesToArray();

    foreach (static::getRecord()->getTranslationsArray() as $key => $value) {
        $data[$key] = $value;
    }

    $data = $this->mutateFormDataBeforeFill($data);

    $this->form->fill($data);

    $this->callHook('afterFill');
}
// This override get SQL optimization and get all translations
protected function getTableQuery(): Builder
{
    return static::getResource()::getEloquentQuery()->with('translations');
}

}

2. For your ModelResource -> Pages -> EditRecords and ListRecords  add your new trait Translatable
3.  In your ModelResource method form(Form $form): Form add translation fields

Forms\Components\TextInput::make('lv.name'), Forms\Components\TextInput::make('lt.name'),

4.  In your ModelResource method table(Table $table): Table add translation column, i show you example, how use searchable() with translations, this search with all locales

Tables\Columns\TextColumn::make('name:lv') ->searchable(query: function (Builder $query, string $search): Builder { return $query->whereTranslationLike('name', "%{$search}%"); }) ->label('Name (LV)'), Tables\Columns\TextColumn::make('name:lt') ->toggleable() ->label('Name (LT)'),

5. In config translatable, you only add array locales, example: ['lv', 'lt']

Now, dont work custom Global search, but... if added this example to ModelResource, Global Search work by default locale and bad SQL optimization...

protected static ?string $recordTitleAttribute = 'translation.name';

public static function getGlobalSearchResultTitle(Model $record): string { // $record->{'name:de'}; return $record->name; }


**Update: How use relationship**
1. First we create ModelRelationManager, how about say documentation Filemanet
2. In methods $form and $table, we create fields and columns, i all copy from ModelResource
3. If you call ModelRelationManager -> actions[] -> method edit(), all translations fields its clear... its problem

**Solution**
1. Add trait Translatable to ModelRelationManager
2.  We change EditAction in ->actions(), use command artisan route:list for get your route. This example open your resource in new tab, if delete openUrlInNewTab() - opened in this page. Example:

Tables\Actions\EditAction::make() ->url(fn (City $record): string => route('filament.resources.cities.edit', $record)) ->openUrlInNewTab(),

3. If you want open modal with translations fields, this example filling default data

Tables\Actions\EditAction::make('modal_edit_with_data') ->mountUsing(function (Forms\ComponentContainer $form, City $record) { $data = []; // Get all data in array $dataRecord = $record->toArray(); // Save in $data by key = value foreach ($dataRecord as $key => $value) { $data[$key] = $value; } // Get all translations in array and save in $data by key = value foreach ($record->getTranslationsArray() as $key => $value) { $data[$key] = $value; } // Filled your data to $form and return return $form->fill($data); }) ->form([......all fields, i copy from ModelResource -> $form().....])

4. Add override method

// This override get SQL optimization and get all translations for ModelRelationManager public function getRelationship(): Relation | Builder { return $this->getOwnerRecord()->{static::getRelationshipName()}()->with(['translations']); }



**It's work**
All methods create, edit, delete and relationships work.

P.S Sorry for my english... ;)
github-actions[bot] commented 1 year ago

This issue is stale because it has been open 21 days with no activity. Remove stale label or comment or this will be closed in 7 days

Dr-Maspix commented 1 year ago

Dear @Solunsky

Thanks For You Topic ,

Can You Please explain the part about Modal as per this part not working ,

I want to edit ModelResource Without Relationship ,

So Please Help Me With This Part

Regards

Solunsky commented 1 year ago

@Dr-Maspix Hello.

Tables\Actions\EditAction::make('modal_edit_with_data')
    ->mountUsing(function (Forms\ComponentContainer $form, City $record) {
        $data = [];
        // Get all data in array
        $dataRecord = $record->toArray();
        // Save in $data by key = value
        foreach ($dataRecord as $key => $value) {
            $data[$key] = $value;
        }
        // Get all translations in array and save in $data by key = value
        foreach ($record->getTranslationsArray() as $key => $value) {
            $data[$key] = $value;
        }
        // Filled your data to $form and return
        return $form->fill($data);
    })
    ->form([])

Here ->form([]) you copy filelds

Dr-Maspix commented 1 year ago

@Solunsky

Thanks For Your Replay here is my code ,

Tables\Actions\EditAction::make('modal_edit_with_data') ->mountUsing(function (Forms\ComponentContainer $form, Product $record) { $data = [];

                    $dataRecord = $record->toArray();

                    foreach ($dataRecord as $key => $value) {
                        $data[$key] = $value;
                    }

                    foreach ($record->getTranslationsArray() as $key => $value) {
                        $data[$key] = $value;
                    }

                    return $form->fill($data);
                })
                ->form([
                    Forms\Components\Toggle::make('is_use')->required(),
                    Forms\Components\TextInput::make('en.name'),
                    Forms\Components\TextInput::make('ar.name'),
                ])

But Still Not Working

image

Solunsky commented 1 year ago

@Dr-Maspix

Test this variant, me work. This solution for [Model]RealtionManager

->actions([
    Tables\Actions\EditAction::make()
        ->mountUsing(function (Forms\ComponentContainer $form, Product $record) {
            $data = [];
            $dataRecord = $record->toArray();

            foreach ($dataRecord as $key => $value) {
                $data[$key] = $value;
            }

            foreach ($record->getTranslationsArray() as $key => $value) {
                $data[$key] = $value;
            }

            return $form->fill($data);
        }),
    Tables\Actions\DeleteAction::make(),
])
Dr-Maspix commented 1 year ago

@Solunsky Thanks For Your Feedback , But i want to deal with resource without [Model]RealtionManager i want to deal with normal resource --simple one , not relation one ,

Regards

Solunsky commented 1 year ago

@Dr-Maspix Ok, see this repo https://github.com/alkarpova/blog Maybe this help you, all my solution you seen

Solunsky commented 1 year ago

@Dr-Maspix I create mini solutrion...

Change EditAction to Action

->actions([
    Tables\Actions\Action::make('edit')
        ->mountUsing(function (Forms\ComponentContainer $form, Category $record) {

            $data = [];
            $dataRecord = $record->toArray();

            foreach ($dataRecord as $key => $value) {
                $data[$key] = $value;
            }

            foreach ($record->getTranslationsArray() as $key => $value) {
                $data[$key] = $value;
            }

            return $form->fill($data);
        })
        ->action(function (array $data): void {
            // $this->record->product()->associate($data['product_id']);
            $this->record->save();
        })
        ->form([
            Forms\Components\TextInput::make( 'en.name')
                ->label('Name'),
            Forms\Components\Toggle::make('status')
                ->default(1),
        ]),
])
Dr-Maspix commented 1 year ago

@Solunsky

OMG It Works ,

We Must Working Together :D

Send Me Your Mail :D

Thnaks My Friend

Dr-Maspix commented 1 year ago

@Solunsky

Have you tried Delete Action before?

image

Solunsky commented 1 year ago

@Dr-Maspix Simple

Tables\Actions\Action::make('delete')
    ->icon('heroicon-o-trash')
    ->action(fn (Model $record) => $record->delete())
    ->color('danger')
    ->requiresConfirmation()
Dr-Maspix commented 1 year ago

@Solunsky

Thanks Dear Solunsky,

ajs321 commented 1 year ago

@Solunsky how did you deal with sorting the translated values in the table? It is giving an 'Unknown Column' error when you sort translated values.

Another thing is filtering translated values does not work as well. Can you please share how you solved it?

omar-bfe commented 6 months ago

Hello All i have my own solution that works with tabs So first create a Custom Layout where we will put our fields for sa translation text called TranslatedText

php artisan make:form-layout TranslatedText This will create 2 file the Componenet file TranslatableText.php and translatable-text.blade.php So far So good Then we will construct the Schema of the Layour programatically. So the Component code looks like this :

TranslatableText.php

class TranslatableText extends Component
{
    protected string $view = 'forms.components.translatable-text';

    protected string | null $fieldType = 'text';

 public function __construct(protected string $name)
    {

    }

    public static function make(string $name): static
    {
        return app(static::class, ['name' => $name]);
    }

    public function getChildComponents(): array
    {
        return $this->ConstructSchema();
    }

    public  function ConstructSchema(): array
    {
        $schema = [Tabs::make($this->name)
            ->tabs(
                collect(Config::get('translation.locales'))
                    ->map(
                        fn ($value) =>
                        Tabs\Tab::make($value)->label(Locale::getDisplayName($value, App::getLocale()))
                            ->schema([TextInput::make($value . '.' . $this->name)->hiddenLabel()->label($value . '.' . $this->name)])
                    )
                    ->toArray()
            )];

        return $schema;
    }

    public function fieldType(string | null $fieldType = 'text'): static
    {
        $this->fieldType = $fieldType;
        return $this;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function getFieldType(): string
    {
        return $this->fieldType;
    }
}

Look at the ConstructSchema Function i'm using the form array syntax from Astronomic translatable

Now in Our translatable-text.blade.php

<div {{ $attributes }}>
    <div x-data="{ state: $wire.$entangle('{{ $getStatePath() }}') }">
        <div class="grid gap-y-2">
            <label class="text-sm font-medium leading-6 text-gray-950 dark:text-white">
                {{$getName()}}
            </label>
            {{ $getChildComponentContainer() }}
        </div>
    </div>
</div>

And finally in our Resource Component We call the layout like that

TranslatableText::make('name')->fieldType('text')->key('translations')

ON EditResourcePage add this

protected function mutateFormDataBeforeFill(array $data): array
{
    foreach ($this->record->getTranslationsArray() as $key => $value) {
        $data[$key] = $value;

    }
    return $data;
}

and all of it is looking like that

Capture d’écran 2024-02-11 à 13 22 19
Gummibeer commented 6 months ago

@omar-bfe could be worth a package. 😉

omar-bfe commented 6 months ago

@Gummibeer I will warp up my solution in a package soon.

MoisesSegura commented 5 months ago

@Solunsky how did you deal with sorting the translated values in the table? It is giving an 'Unknown Column' error when you sort translated values.

Another thing is filtering translated values does not work as well. Can you please share how you solved it?

did you find any solution to this problem?

Oleksandr-Moik commented 4 months ago

I want to let everyone know that I found a solution to the problem. I made the package for that. At the moment there is support only through tabs, but I think we will add support through the switch later, as it is done in the package from Spatie.

Package - https://github.com/CactusGalaxy/FilamentAstrotomic

image image

Example from demo repository - https://github.com/CactusGalaxy/GalaxyStoreExample/blob/main/app/Filament/Resources/BannerResource.php