cactus-galaxy / filament-astrotomic
Filament support for Astrotomic's Laravel Translatable package.
Fund package maintenance!
CactusGalaxy
Installs: 1 970
Dependents: 0
Suggesters: 0
Security: 0
Stars: 12
Watchers: 0
Forks: 2
Open Issues: 2
Requires
- php: ^8.1
- astrotomic/laravel-translatable: ^11.12
- filament/filament: ^3.0
- laravel/framework: ^10.0 || ^11.0
- spatie/laravel-package-tools: ^1.15.0
Requires (Dev)
- laravel/pint: ^1.0
- nunomaduro/collision: ^7.9
- orchestra/testbench: ^8.0 || ^9.0
- pestphp/pest: ^2.0
- pestphp/pest-plugin-arch: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
This package is auto-updated.
Last update: 2024-11-09 00:52:59 UTC
README
This package is an extension for Filament and laravel-translatable.
Inspired by spatie/laravel-translatable, another alternative for the translation package for your Laravel application.
See more configuration examples of the Filament panel in GalaxyStore - demo project with Filament, Astrotomic package and more.
Installation
You can install the package via Composer:
composer require cactus-galaxy/filament-astrotomic
Publish configs for astrotomic/laravel-translatable
package:
php artisan vendor:publish --tag="translatable"
After this, you will have to configure the locales your app should use.
'locales' => [ 'uk', 'en', ],
Adding the plugin to a panel
To add a plugin to a panel, you must include it in the configuration file using the plugins()
method:
use CactusGalaxy\FilamentAstrotomic\FilamentAstrotomicTranslatablePlugin; public function panel(Panel $panel): Panel { return $panel // ... ->plugins([ FilamentAstrotomicTranslatablePlugin::make(), ]); }
Preparing your model class
You need to make your model translatable. You can read how to do this in documentation for Laravel translatable.
Preparing your resource class
You must apply the ResourceTranslatable
trait to your resource class:
use CactusGalaxy\FilamentAstrotomic\Resources\Concerns\ResourceTranslatable; use Filament\Resources\Resource; class ProductResource extends Resource { use ResourceTranslatable; // ... }
Making resource pages translatable
After preparing your resource class, you must make each of your resource's pages translatable too.
You can find your resource's pages in the Pages
directory of each resource folder.
To prepare a page, you must apply the corresponding {Type}Translatable
trait to it:
use CactusGalaxy\FilamentAstrotomic\Resources\Pages\Record\ListTranslatable; use Filament\Resources\Pages\ListRecords; class ListProducts extends ListRecords { use ListTranslatable; // ... }
use CactusGalaxy\FilamentAstrotomic\Resources\Pages\Record\CreateTranslatable; use Filament\Resources\Pages\CreateRecord; class CreateProduct extends CreateRecord { use CreateTranslatable; // ... }
use CactusGalaxy\FilamentAstrotomic\Resources\Pages\Record\EditTranslatable; use Filament\Resources\Pages\EditRecord; class EditProduct extends EditRecord { use EditTranslatable; // ... }
And if you have a ViewRecord
page for your resource:
use CactusGalaxy\FilamentAstrotomic\Resources\Pages\Record\ViewTranslatable; use Filament\Resources\Pages\ViewRecord; class ViewProduct extends ViewRecord { use ViewTranslatable; // ... }
Setting the translatable locales for a particular resource
By default, the translatable locales loaded using Astrotomic's Locales helper from method all()
which returns all locales from the translatable.locales
configuration.
Alternatively, you can customize the translatable locales for a particular resource by overriding the getTranslatableLocales()
method in your resource class:
use CactusGalaxy\FilamentAstrotomic\Resources\Concerns\ResourceTranslatable; use Filament\Resources\Resource; class ProductResource extends Resource { use ResourceTranslatable; // ... public static function getTranslatableLocales(): array { return ['uk', 'en']; } }
Using locale tabs on the form
TranslatableTabs
extends the default Filament\Forms\Components\Tabs
component and provides a way to create tab schema tabs for each locale.
Within the localeTabSchema
method, you can define the callback for schema for each tab.
This callback will be called for each locale to generate scheme for tab.
localeTabSchema
supports utility injection
To accept the TranslatableTab
instance as an argument to get the current locale you need to name argument as $translatableTab
or use type hint, like in example bellow.
Here is an example of how to use TranslatableTabs
in the ProductResource
form:
use CactusGalaxy\FilamentAstrotomic\Forms\Components\TranslatableTabs; use CactusGalaxy\FilamentAstrotomic\Resources\Concerns\ResourceTranslatable; use CactusGalaxy\FilamentAstrotomic\TranslatableTab; use Filament\Forms\Form; use Filament\Resources\Resource; class ProductResource extends Resource { use ResourceTranslatable; // ... public static function form(Form $form): Form { return $form->columns(1)->schema([ Forms\Components\TextInput::make('slug') ->unique(ignoreRecord: true) ->required() ->readOnly() ->helperText('Генерується автоматично при зміні назви') ->maxLength(255), TranslatableTabs::make() ->localeTabSchema(fn (TranslatableTab $tab) => [ Forms\Components\TextInput::make($tab->makeName('name')) // required only for the main locale ->required($tab->isMainLocale()) ->maxLength(255) // generate slug for the item based on the main locale ->live(onBlur: true) ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) use ($tab) { if ($tab->isMainLocale()) { $set('slug', Str::slug($state)); } }), ]), // ... ]); } // ... }
With this code, you will get tabs for each locale with name
field in each tab. name
field will be required only for the main locale.
Also, it will generate a slug for the item based on the main locale.
By default $tab->makeName('name')
uses array syntax for naming -{$locale}.{$name}
, but you can change it by calling makeNameUsing
on TranslatableTabs
, for example, use plain syntax:
TranslatableTabs::make() // plain syntax ->makeNameUsing(fn (string $name, string $locale) => "{$name}:{$locale}") // or use an alias ->makeNameUsingPlainSyntax() // ..
Prepend or append tabs
Sometimes you need to add tabs before or after the localized tabs. You can use the prependTabs
and appendTabs
methods for this:
use Filament\Forms\Components\Tabs\Tab; TranslatableTabs::make() ->localeTabSchema(fn (TranslatableTab $tab) => [ // ... ]) ->prependTabs([ Tab::make('Tab before localized') ->schema([ // ... ]) // ... ]) ->appendTabs(fn () => [ // you also can pass a callback or array Tab::make('Tab after localized') ->schema([ // ... ]) // ... ])
Processing modal forms with translations
If you want to use translations in modal forms, you need to make some changes, to correctly mutate and fill your form.
Edit table action
For example, we have ProductResource
but don't have an edit page.
To process translations in the Edit action modal, you need to override the mutateRecordDataUsing
method of the EditAction
class in the resource class.
And if you are using a Column with path translation.*
, make sure to unset the translation
relation from the record data before returning it, otherwise, the record data will be saved incorrectly.
use App\Models\Product; use CactusGalaxy\FilamentAstrotomic\Forms\Components\TranslatableTabs; use CactusGalaxy\FilamentAstrotomic\Resources\Concerns\ResourceTranslatable; use Filament\Forms\Form; use Filament\Tables; use Filament\Tables\Table; class ProductResource extends Resource { use ResourceTranslatable; // ... public static function form(Form $form): Form { // ... form with `TranslatableTabs` } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('translation.name'), // ... ]) // ... ->actions([ Tables\Actions\EditAction::make()->mutateRecordDataUsing(function (Product $record, array $data) { return self::mutateTranslatableData($record, $data); })->mutateFormDataUsing(function (Product $record, array $data) { $record->unsetRelation('translation'); return $data; }), // ... ]); } public static function getPages(): array { return [ 'index' => Pages\ListProducts::route('/'), // edit route is missing ]; }
Select with modal options
There is a more complex example with Select
component.
For example, we need to manage (create or edit) a category for the product.
In the CategoryResource
defined form with translatable fields.
use CactusGalaxy\FilamentAstrotomic\Forms\Components\TranslatableTabs; use CactusGalaxy\FilamentAstrotomic\Resources\Concerns\ResourceTranslatable; use CactusGalaxy\FilamentAstrotomic\TranslatableTab; use Filament\Forms; use Filament\Forms\Form; class CategoryResource extends Resource { use ResourceTranslatable; // ... public static function form(Form $form): Form { return $form ->schema([ TranslatableTabs::make() ->localeTabSchema(fn (TranslatableTab $tab) => [ Forms\Components\TextInput::make($tab->makeName('name')) ->required($tab->isMainLocale()) ->maxLength(255), ]), ]); } // ... }
Then is ProductResource
we can use Select
component with modal options. Note that we need to call the fillEditOptionActionFormUsing
method and mutate record data
use App\Models\Category; use Filament\Forms; use Filament\Forms\For; Forms\Components\Select::make('category_id') ->required() ->native(false) ->searchable() ->options( Category::query() ->joinTranslations() ->pluck('name', 'categories.id') ->toArray() ) // Configure create action - https://filamentphp.com/docs/3.x/forms/fields/select#creating-a-new-option-in-a-modal ->createOptionModalHeading('Create') ->createOptionForm(fn (Form $form) => CategoryResource::form($form)) ->createOptionUsing(function (array $data) { $optionRecord = Category::create($data); return $optionRecord->id; }) // Configure edit action - https://filamentphp.com/docs/3.x/forms/fields/select#editing-the-selected-option-in-a-modal ->editOptionModalHeading('Edit') ->editOptionForm(fn (Form $form) => CategoryResource::form($form)) ->fillEditOptionActionFormUsing(function (string $state) { if (!$state) { return []; } $optionRecord = Category::find($state); return CategoryResource::mutateTranslatableData($record, $record->attributesToArray()); }) ->updateOptionUsing(function (array $data, string $state) { $optionRecord = Category::find($state); $optionRecord->update($data); return $optionRecord->id; })
Columns for translatable models on listings
Out of the box, Filament supports nesting for columns, which means you can use .
in the column path to access nested properties, and you don't need a special column for translatable models.
Tables\Columns\TextColumn::make('translation.name'),
But searching by translatable column is more complicated to write for each text column. To solve and to add an option to search by column, we recommend adding the following lines to configure your column to your ServiceProvider in boot method.
From FilamentServiceProvider of
GalaxyStore
use Astrotomic\Translatable\Translatable; use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; // in `boot` method TextColumn::configureUsing(function (TextColumn $column): void { // match `translations.title` or `translation.title` if (Str::match('@^translations?\.(\w+)$@', $column->getName())) { $column ->searchable(query: function (Builder $query, string $search) use ($column): Builder { $columnName = Str::after($column->getName(), '.'); if ($query->hasNamedScope('whereTranslationLike')) { /* @var Translatable|TranslatableContract $query */ return $query->whereTranslationLike($columnName, "%{$search}%"); } return $query->where($columnName, 'like', "%{$search}%"); }); } });
Maybe in the feature someone will create a column for that 🙂, but currently you can use this code to configure your columns.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
As quick example, you can clone GalaxyStore and this package to one folder
Projects
|-FilamentAstrotomic
|-GalaxyStore
Add repositories
section to your GalaxyStore/composer.json
file
{ "repositories": [ { "type": "path", "url": "../FilamentAstrotomic", "options": { "symlink": true } } ] }
Then execute composer
composer require "cactus-galaxy/filament-astrotomic:@dev"
or manually add package to composer.json
file
{ "require": { "cactus-galaxy/filament-astrotomic": "@dev" } }
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.