Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cached translation support for LibraryTypes and TagTypes. #163

Merged
merged 4 commits into from
Oct 5, 2023

Conversation

BurakBoz
Copy link
Contributor

This PR adds fast cached translation support for libraryTypes and tagTypes.

@atmonshi
Copy link
Member

atmonshi commented Oct 4, 2023

Thank you @BurakBoz
How to use the translatable?
because I get:

Target class [translator] does not exist.

when adding translatable:

->libraryTypes([
      'FILE' => __('File'),
      'IMAGE' => __('Image'),
      'VIDEO' => __('Video'),
  ])

also won't be better to allow to pass a Closure!

@BurakBoz
Copy link
Contributor Author

BurakBoz commented Oct 5, 2023

Hello! Here is my config/app.php. I believe this will resolve your error.

'providers' => ServiceProvider::defaultProviders()->merge([
        /*
         * Package Service Providers...
         */
        Illuminate\Translation\TranslationServiceProvider::class, // <== Translation Service

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\Filament\AdminPanelProvider::class,
        App\Providers\RouteServiceProvider::class,
    ])->toArray(),

When it comes to why I'm using cache, I noticed that it was being called three times. I implemented this structure to avoid unnecessary repetitive processing.

I'm using without __ function on my AdminPanelProvider

->libraryTypes([
                    'FILE' => 'File',
                    'IMAGE' => 'Image',
                    'VIDEO' => 'Video',
                ])
                ->tagTypes([
                    'tag' => 'Tag',
                    'category' => 'Category',
                    'library' => 'Library',
                    'faq' => 'Faq',
                ]))

I've also created a Trait for this that adds support for untranslatable plugin translations:

<?php

namespace BurakBoz\Filament\Support;

use Filament\Forms\Components\Field;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Toggle;
use Filament\Navigation\NavigationGroup;
use Filament\Navigation\NavigationItem;
use Filament\Tables\Columns\Column;
use Filament\Tables\Filters\BaseFilter;

trait TranslateLabels
{
    public function autoTranslate(): void
    {
        $this->translateLabels([
            NavigationGroup::class,
            NavigationItem::class,
            Field::class,
            BaseFilter::class,
            Placeholder::class,
            Column::class,
            Toggle::class
        ]);
    }

    private function translateLabels(array $components = []): void
    {
        foreach($components as $component)
        {
            if(class_exists($component))
            {
                $component::configureUsing(static function ($c): void
                {
                    method_exists($c, 'translateLabel') && $c->translateLabel();
                    method_exists($c, 'label') && $c->label(__($c->getLabel()));
                });
            }
        }
    }
}

Using it in my AppServiceProvider

class AppServiceProvider extends ServiceProvider
{
    use TranslateLabels;

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->autoTranslate();
    }

Perhaps there are better alternatives to accomplish this; I would appreciate suggestions.

@atmonshi
Copy link
Member

atmonshi commented Oct 5, 2023

that is a nice solution for translating packages :)

I would do it by passing a Closure the same way for other configurations like skyPrefix

    protected Closure| array | null $libraryTypes = [
        'FILE' => 'File',
        'IMAGE' => 'Image',
        'VIDEO' => 'Video',
    ];
    
    public function libraryTypes(Closure|array $types): static
    {
        $this->libraryTypes = $types;

        return $this;
    }

    public function getLibraryTypes(): Closure|array|null
    {
        return $this->evaluate($this->libraryTypes);
    }

and then in your panel provider:

->libraryTypes(fn()=>[
      'FILE' => __('File'),
      'IMAGE' => __('Image'),
      'VIDEO' => __('Video'),
  ])

but I'm not sure about being called three times, does it happen even if you cache the views?

@BurakBoz
Copy link
Contributor Author

BurakBoz commented Oct 5, 2023

Closures

This is the way

I believe this approach makes more sense. I will test the cache

@BurakBoz
Copy link
Contributor Author

BurakBoz commented Oct 5, 2023

    public function getLibraryTypes(): ?array
    {
        !isset($GLOBALS["i"]) && $GLOBALS["i"] = 0; dump($GLOBALS["i"]++, debug_backtrace(limit:1));
        return $this->libraryTypes;
    }
php artisan cache:clear
php artisan optimize:clear
php artisan optimize
php artisan config:cache
php artisan route:cache
php artisan event:cache
php artisan icons:clear
php artisan icons:cache
php artisan view:cache

In a cached app, this results in 7 calls to the /admin/library/create URL in LibraryResource.php:79 and LibraryResource.php:78.

If each call executes the translate function, it can significantly slow down the application.

Here is the patched version of LibraryResource that reduces the number of calls to only 3.

<?php

namespace LaraZeus\Sky\Filament\Resources;

use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\SpatieMediaLibraryFileUpload;
use Filament\Forms\Components\SpatieTagsInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\SpatieTagsColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use LaraZeus\Sky\Filament\Resources\LibraryResource\Pages;
use LaraZeus\Sky\Models\Library;
use LaraZeus\Sky\SkyPlugin;
use Wallo\FilamentSelectify\Components\ButtonGroup;

class LibraryResource extends SkyResource
{
    protected static ?string $slug = 'library';

    protected static ?string $navigationIcon = 'heroicon-o-folder';

    protected static ?int $navigationSort = 4;

    public static function getModel(): string
    {
        return SkyPlugin::get()->getModel('Library');
    }

    public static function form(Form $form): Form
    {
        $libraryTypes = SkyPlugin::get()->getLibraryTypes();
        return $form
            ->schema([
                Section::make(__('Library File'))
                    ->columns(2)
                    ->schema([
                        TextInput::make('title')
                            ->label(__('Library Title'))
                            ->required()
                            ->maxLength(255)
                            ->live(onBlur: true)
                            ->afterStateUpdated(function (Set $set, $state, $context) {
                                if ($context === 'edit') {
                                    return;
                                }

                                $set('slug', Str::slug($state));
                            }),

                        TextInput::make('slug')
                            ->unique(ignorable: fn (?Library $record): ?Library => $record)
                            ->required()
                            ->maxLength(255)
                            ->label(__('Library Slug')),

                        Textarea::make('description')
                            ->maxLength(255)
                            ->label(__('Library Description'))
                            ->columnSpan(2),

                        SpatieTagsInput::make('category')
                            ->type('library')
                            ->label(__('Library Categories')),

                        Select::make('type')
                            ->label(__('Library Type'))
                            ->visible($libraryTypes !== null)
                            ->options($libraryTypes),
                    ]),

                Section::make(__('Library File'))
                    ->collapsible()
                    ->compact()
                    ->schema([
                        ButtonGroup::make('upload_or_url')
                            ->live()
                            ->dehydrated(false)
                            ->afterStateHydrated(function (Set $set, Get $get) {
                                $setVal = ($get('file_path') === null) ? 'upload' : 'url';
                                $set('upload_or_url', $setVal);
                            })
                            ->options([
                                'upload' => __('upload'),
                                'url' => __('url'),
                            ])
                            ->default('upload'),
                        SpatieMediaLibraryFileUpload::make('file_path_upload')
                            ->disk(SkyPlugin::get()->getUploadDisk())
                            ->directory(SkyPlugin::get()->getUploadDirectory())
                            ->collection('library')
                            ->multiple()
                            ->reorderable()
                            ->visible(fn (Get $get) => $get('upload_or_url') === 'upload')
                            ->label(''),

                        TextInput::make('file_path')
                            ->label(__('file url'))
                            ->visible(fn (Get $get) => $get('upload_or_url') === 'url')
                            ->url(),
                    ]),
            ]);
    }

    public static function table(Table $table): Table
    {
        $libraryTypes = SkyPlugin::get()->getLibraryTypes();
        return $table
            ->columns([
                TextColumn::make('title')->label(__('Library Title'))->searchable()->sortable()->toggleable(),
                TextColumn::make('slug')->label(__('Library Slug'))->searchable()->sortable()->toggleable(),

                TextColumn::make('type')
                    ->label(__('Library Type'))
                    ->searchable()
                    ->sortable()
                    ->visible($libraryTypes !== null)
                    ->formatStateUsing(fn (string $state): string => str($state)->title())
                    ->color('')
                    ->color(fn (string $state) => match ($state) {
                        'IMAGE' => 'primary',
                        'FILE' => 'success',
                        'VIDEO' => 'warning',
                        default => '',
                    })
                    ->icon(fn (string $state) => match ($state) {
                        'IMAGE' => 'heroicon-o-photo',
                        'FILE' => 'heroicon-o-document',
                        'VIDEO' => 'heroicon-o-film',
                        default => 'heroicon-o-document-magnifying-glass',
                    })
                    ->toggleable(),

                SpatieTagsColumn::make('tags')
                    ->label(__('Library Tags'))
                    ->toggleable()
                    ->type('library'),
            ])
            ->actions([
                ActionGroup::make([
                    EditAction::make('edit')->label(__('Edit')),
                    Action::make('Open')
                        ->color('warning')
                        ->icon('heroicon-o-arrow-top-right-on-square')
                        ->label(__('Open'))
                        ->url(fn (Library $record): string => route('library.item', ['slug' => $record->slug]))
                        ->openUrlInNewTab(),
                    DeleteAction::make('delete')
                        ->label(__('Delete')),
                ]),
            ])
            ->filters([
                SelectFilter::make('type')
                    ->visible()
                    ->options($libraryTypes)
                    ->visible($libraryTypes !== null)
                    ->label(__('type')),
                SelectFilter::make('tags')
                    ->multiple()
                    ->relationship('tags', 'name')
                    ->label(__('Tags')),
            ])
            ->defaultSort('id', 'desc');
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListLibrary::route('/'),
            'create' => Pages\CreateLibrary::route('/create'),
            'edit' => Pages\EditLibrary::route('/{record}/edit'),
        ];
    }

    public static function getLabel(): string
    {
        return __('Library');
    }

    public static function getPluralLabel(): string
    {
        return __('Libraries');
    }

    public static function getNavigationLabel(): string
    {
        return __('Libraries');
    }
}

Closures can be useful for configuration, but they still require a temporary caching solution for translations.

I won't be submitting any more PRs or comments on this topic.

Let's, at the very least, include the Turkish language translation that I have submitted to the package.

@atmonshi
Copy link
Member

atmonshi commented Oct 5, 2023

No problem :).
Thank you again,
I will re-visit this and see how to improve it more and enhance the performance

@atmonshi atmonshi merged commit d5c2116 into lara-zeus:3.x Oct 5, 2023
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants