├── .gitignore ├── docs └── images │ └── screenshot.png ├── src ├── FilamentPluginTranslatableInlineServiceProvider.php └── Forms │ └── Components │ └── TranslatableContainer.php ├── LICENSE.md ├── CHANGELOG.md ├── composer.json ├── resources └── views │ └── forms │ └── components │ └── translatable-container.blade.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /docs/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvenghaus/filament-plugin-translatable-inline/HEAD/docs/images/screenshot.png -------------------------------------------------------------------------------- /src/FilamentPluginTranslatableInlineServiceProvider.php: -------------------------------------------------------------------------------- 1 | name(static::$name); 19 | 20 | if (file_exists($package->basePath('/../resources/views'))) { 21 | $package->hasViews(static::$viewNamespace); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Marcus Venghaus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `mvenghaus/filament-plugin-translatable-inline` will be documented in this file. 4 | 5 | ## 3.0.8 - 2024-03-16 6 | - added support for table repeater 7 | 8 | ## 3.0.7 - 2024-02-27 9 | - fixed getResource issue in pages & relation manager 10 | 11 | ## 3.0.6 - 2024-02-09 12 | - added handling for "getTranslatableLocales" in resource 13 | 14 | ## 3.0.5 - 2024-02-08 15 | - changed afterStateUpdated locale from name -> meta 16 | 17 | ## 3.0.4 - 2024-02-03 18 | - added requiredLocale [#3](https://github.com/mvenghaus/filament-plugin-translatable-inline/issues/3) 19 | - fixed issue when nothing is required [#2](https://github.com/mvenghaus/filament-plugin-translatable-inline/issues/2) 20 | - changed svg icons to blade icons 21 | - added locale as component name to get the current locale in afterStateUpdated and other methods 22 | 23 | ## 3.0.3 - 2024-01-16 24 | - refactored onlyMainIsRequired 25 | - improved validation handling [#1](https://github.com/mvenghaus/filament-plugin-translatable-inline/issues/1) 26 | - little design upgrade 27 | 28 | ## 3.0.2 - 2024-01-16 29 | - fixed issue when using afterStateUpdated on additional locales 30 | 31 | ## 3.0.1 - 2024-01-14 32 | - fixed issue when using .live() 33 | 34 | ## 3.0.0 - 2024-01-07 35 | 36 | - initial release 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mvenghaus/filament-plugin-translatable-inline", 3 | "description": "An alternative to https://github.com/filamentphp/spatie-laravel-translatable-plugin. The difference is that all translations occur directly under each field.", 4 | "keywords": [ 5 | "mvenghaus", 6 | "laravel", 7 | "filament", 8 | "translatable", 9 | "spatie", 10 | "inline" 11 | ], 12 | "homepage": "https://github.com/mvenghaus/filament-plugin-translatable-inline", 13 | "support": { 14 | "issues": "https://github.com/mvenghaus/filament-plugin-translatable-inline", 15 | "source": "https://github.com/mvenghaus/filament-plugin-translatable-inline" 16 | }, 17 | "license": "MIT", 18 | "authors": [ 19 | { 20 | "name": "Marcus Venghaus", 21 | "email": "marcus.venghaus@inklammern.de", 22 | "role": "Developer" 23 | } 24 | ], 25 | "require": { 26 | "php": "^8.2", 27 | "filament/forms": "^3.0", 28 | "filament/spatie-laravel-translatable-plugin": "^3.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Mvenghaus\\FilamentPluginTranslatableInline\\": "src/" 33 | } 34 | }, 35 | "config": { 36 | "sort-packages": true 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "Mvenghaus\\FilamentPluginTranslatableInline\\FilamentPluginTranslatableInlineServiceProvider" 42 | ] 43 | } 44 | }, 45 | "minimum-stability": "stable", 46 | "prefer-stable": true 47 | } -------------------------------------------------------------------------------- /resources/views/forms/components/translatable-container.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
25 |
26 | {{ $getChildComponentContainer('main') }} 27 |
28 | 29 |
32 |
33 | 34 |
35 | 36 |
37 | 38 |
39 | 40 | @foreach($getTranslatableLocales() as $locale) 41 |
46 |
{{ $locale }}
47 |
48 | @endforeach 49 |
50 | 51 |
54 |
55 | {{ $getChildComponentContainer('additional') }} 56 |
57 |
58 |
59 |
-------------------------------------------------------------------------------- /src/Forms/Components/TranslatableContainer.php: -------------------------------------------------------------------------------- 1 | schema($schema); 23 | 24 | $this->baseComponent = collect($schema)->first(); 25 | $this->statePath($this->baseComponent->getName()); 26 | } 27 | 28 | public static function make(Component $component): static 29 | { 30 | $static = app(static::class, [ 31 | 'schema' => [$component] 32 | ]); 33 | $static->configure(); 34 | 35 | return $static; 36 | } 37 | 38 | public function getName(): string 39 | { 40 | return $this->baseComponent->getName(); 41 | } 42 | 43 | public function getLabel(): string 44 | { 45 | return $this->baseComponent->getLabel(); 46 | } 47 | 48 | public function getChildComponentContainers(bool $withHidden = false): array 49 | { 50 | $locales = $this->getTranslatableLocales(); 51 | 52 | $containers = []; 53 | 54 | $containers['main'] = ComponentContainer::make($this->getLivewire()) 55 | ->parentComponent($this) 56 | ->components([ 57 | $this->cloneComponent($this->baseComponent, $locales->first()) 58 | ->required($this->isLocaleRequired($locales->first())) 59 | ]); 60 | 61 | $containers['additional'] = ComponentContainer::make($this->getLivewire()) 62 | ->parentComponent($this) 63 | ->components( 64 | $locales 65 | ->filter(fn(string $locale, int $index) => $index !== 0) 66 | ->map( 67 | fn(string $locale): Component => $this->cloneComponent($this->baseComponent, $locale) 68 | ->required($this->isLocaleRequired($locale)) 69 | ) 70 | ->all() 71 | ); 72 | 73 | return $containers; 74 | } 75 | 76 | public function cloneComponent(Component $component, string $locale): Component 77 | { 78 | return $component 79 | ->getClone() 80 | ->meta('locale', $locale) 81 | ->label("{$component->getLabel()} ({$locale})") 82 | ->statePath($locale); 83 | } 84 | 85 | public function getTranslatableLocales(): Collection 86 | { 87 | $resourceLocales = null; 88 | if (method_exists($this->getLivewire(), 'getResource') && 89 | method_exists($this->getLivewire()::getResource(), 'getTranslatableLocales') 90 | ) { 91 | $resourceLocales = $this->getLivewire()::getResource()::getTranslatableLocales(); 92 | } 93 | 94 | return collect($resourceLocales ?? filament('spatie-laravel-translatable')->getDefaultLocales()); 95 | } 96 | 97 | public function isLocaleStateEmpty(string $locale): bool 98 | { 99 | return empty($this->getState()[$locale]); 100 | } 101 | 102 | public function onlyMainLocaleRequired(): self 103 | { 104 | $this->onlyMainIsRequired = true; 105 | 106 | return $this; 107 | } 108 | 109 | public function requiredLocales(array $locales): self 110 | { 111 | $this->requiredLocales = $locales; 112 | 113 | return $this; 114 | } 115 | 116 | private function isLocaleRequired(string $locale): bool 117 | { 118 | if ($this->onlyMainIsRequired) { 119 | return ($locale === $this->getTranslatableLocales()->first()); 120 | } 121 | 122 | if (in_array($locale, $this->requiredLocales)) { 123 | return true; 124 | } 125 | 126 | if (empty($this->requiredLocales) && $this->baseComponent->isRequired()) { 127 | return true; 128 | } 129 | 130 | return false; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filament Plugin - Translatable Inline 2 | 3 | This is an addon to [Spatie Translatable](https://filamentphp.com/plugins/filament-spatie-translatable) that allows you to edit your translation directly below the field. 4 | 5 | This approach offers several advantages: 6 | 7 | - Faster editing of your translations 8 | - Detecting fields that can be translated is much easier to see 9 | - You can quickly see which translations are missing 10 | 11 | ## Screenshots 12 | 13 | ![Screenshot](https://raw.githubusercontent.com/mvenghaus/filament-plugin-translatable-inline/main/docs/images/screenshot.png) 14 | 15 | ## Requirements 16 | 17 | You need the latest version of Filament v3. 18 | 19 | This package is based on: 20 | - [Spatie Laravel Translatable](https://github.com/spatie/laravel-translatable) 21 | - [Filament Spatie Translatable Plugin](https://github.com/filamentphp/spatie-laravel-translatable-plugin) 22 | 23 | You don't need to install them separately, it's handled via dependencies. 24 | 25 | ## Installation 26 | 27 | Install the package via composer: 28 | 29 | ```bash 30 | composer require mvenghaus/filament-plugin-translatable-inline:"^3.0" 31 | ``` 32 | 33 | ### Configuration 34 | 35 | Since it is based on the Spatie plugin, it must be registered as described in the [documentation](https://github.com/filamentphp/spatie-laravel-translatable-plugin). 36 | 37 | > **_NOTE:_** It is important that you don't add the traits and the header action to your form resource pages, or it won't work! Only the trait "Translatable" in your resource is required! 38 | 39 | Instead of having a locale switcher in a dropdown above, you add a container for each translatable field. 40 | 41 | **Before** 42 | ```php 43 | schema([ 51 | Forms\Components\TextInput::make('title') 52 | ->maxLength(255) 53 | ->required() 54 | , 55 | 56 | ... 57 | ``` 58 | 59 | **After** 60 | ```php 61 | schema([ 73 | TranslatableContainer::make( 74 | Forms\Components\TextInput::make('title') 75 | ->maxLength(255) 76 | ->required() 77 | ) 78 | ->onlyMainLocaleRequired() // optional 79 | ->requiredLocales(['en', 'es']) // optional 80 | , 81 | 82 | ... 83 | ``` 84 | 85 | For each field that can be translated, simply repeat this process, and you'll be done. 86 | 87 | > **_NOTE:_** You don't have to globally choose between inline or dropdown. Instead, you can choose an option on each page. For instance, it makes sense to have the dropdown in the list view and then use the inline version when editing. 88 | 89 | ### Options 90 | 91 | #### onlyMainLocaleRequired 92 | 93 | Sometimes you might want the field to be required, but only for the primary language. For example, if you set the TextInput to 'required,' it applies to all language variants. This is where this option comes into play. It removes the 'required' validation for all other languages except the primary one. 94 | 95 | #### requireLocales 96 | 97 | If you have more than one required locales you can pass an array to this method. 98 | 99 | ## Tipps & Hints 100 | 101 | ### Validation 102 | 103 | If all of your locales are required and if your values do not pass the JS validation, then the variants will remain automatically expanded. 104 | 105 | ### afterStateUpdated 106 | 107 | If you want to use "afterStateUpdated", you have to consider that the state path shifts by one level. 108 | n addition, one must specify the locale which is located in the component's meta under the key "locale". 109 | 110 | **Before** 111 | ```php 112 | ->afterStateUpdated(fn (Set $set, ?string $state) => $set('slug', Str::slug($state))), 113 | ``` 114 | 115 | **After** 116 | ```php 117 | ->afterStateUpdated(fn (Set $set, Component $component, ?string $state) => $set('../slug.' . $component->getMeta('locale'), Str::slug($state))), 118 | ``` 119 | 120 | ### Empty translations 121 | 122 | ![Screenshot](https://raw.githubusercontent.com/mvenghaus/filament-plugin-translatable-inline/main/docs/images/screenshot.png) 123 | 124 | As you can see in the screenshot, the "nl" is not filled and therefore not marked. 125 | 126 | # Contact 127 | If you any questions or you find a bug, please [contact me via email](mailto:support@inklammern.de). --------------------------------------------------------------------------------