In this lesson, we are going to re-create the same scenario as in the previous lesson, but using Livewire to create the form with more dynamic elements.
Our base logic and setup are the same as in the previous lesson, so we will skip that part and start modifying the forms to support a nice user experience.
To install Livewire, we need to run the following command:
composer require livewire/livewire
Now we are ready to use Livewire.
For this example, we'll create a single component that will handle switching languages and form inputs:
php artisan make:livewire PostContentPerLanguage
With the following code on the PHP side:
app/Http/Livewire/PostContentPerLanguage.php
use App\Models\Post;use Livewire\Component; class PostContentPerLanguage extends Component{ public ?Post $post; public string $currentLanguage; public array $languagesList; public function mount(): void { $this->currentLanguage = app()->getLocale(); $this->languagesList = config('app.supportedLocales', []); } public function render() { return view('livewire.post-content-per-language'); } public function changeLocale($locale): void { if (in_array($locale, $this->languagesList)) { $this->currentLanguage = $locale; } }}
And the following code on View:
resources/views/livewire/post-content-per-language.blade.php
<div> <div class="w-full mb-4"> @foreach($languagesList as $locale) <button type="button" wire:click="changeLocale('{{ $locale }}')" @class([ 'bg-blue-500 text-white px-4 py-2 rounded font-medium inline' => $locale == $currentLanguage, 'bg-gray-200 text-gray-500 px-4 py-2 rounded font-medium inline' => $locale != $currentLanguage, ]) > Translations for: {{ $locale }} </button> @endforeach </div> @foreach($languagesList as $locale) <fieldset @class(['hidden' => $locale != $currentLanguage, 'border-2 w-full p-4 rounded-lg mb-4'])> <div class="mb-4"> <label for="title[{{$locale}}]" class="sr-only">Title</label> <input type="text" name="title[{{$locale}}]" id="title[{{$locale}}]" placeholder="Title" class="bg-gray-100 border-2 w-full p-4 rounded-lg @error('title') border-red-500 @enderror" value="{{ old('title.'. $locale, $post?->translations->where('locale', $locale)->first()?->title) }}"> @error('title.'.$locale) <div class="text-red-500 mt-2 text-sm"> {{ $message }} </div> @enderror </div> <div class=""> <label for="post[{{$locale}}]" class="sr-only">Body</label> <textarea name="post[{{$locale}}]" id="post[{{$locale}}]" cols="30" rows="4" placeholder="Post" class="bg-gray-100 border-2 w-full p-4 rounded-lg @error('post'.$locale) border-red-500 @enderror">{{ old('post'.$locale, $post?->translations->where('locale', $locale)->first()?->post) }}</textarea> @error('post.'.$locale) <div class="text-red-500 mt-2 text-sm"> {{ $message }} </div> @enderror </div> </fieldset> @endforeach</div>
The next step is to modify our form to use this component:
resources/views/posts/create.blade.php
<form action="{{ route('posts.store') }}" method="POST"> @csrf <div class="mb-4"> <label for="author_id" class="sr-only">Author</label> <select name="author_id" id="author_id" class="bg-gray-100 border-2 w-full p-4 rounded-lg @error('author_id') border-red-500 @enderror"> <option value="">Select author</option> @foreach($authors as $id => $name) <option value="{{ $id }}">{{ $name }}</option> @endforeach </select> @error('author_id') <div class="text-red-500 mt-2 text-sm"> {{ $message }} </div> @enderror </div> <livewire:post-content-per-language/> <div class="mb-4"> <label for="publish_date" class="sr-only">Published at</label> <input type="datetime-local" name="publish_date" id="publish_date" placeholder="Published at" class="bg-gray-100 border-2 w-full p-4 rounded-lg @error('publish_date') border-red-500 @enderror" value="{{ old('publish_date') }}"> @error('publish_date') <div class="text-red-500 mt-2 text-sm"> {{ $message }} </div> @enderror </div> <div> <button type="submit" class="bg-blue-500 text-white px-4 py-3 rounded font-medium w-full"> Create </button> </div></form>
And edit view:
resources/views/posts/edit.blade.php
<form action="{{ route('posts.update', $post->id) }}" method="POST"> @csrf @method('PUT') <div class="mb-4"> <label for="author_id" class="sr-only">Author</label> <select name="author_id" id="author_id" class="bg-gray-100 border-2 w-full p-4 rounded-lg @error('author_id') border-red-500 @enderror"> <option value="">Select author</option> @foreach($authors as $id => $name) <option value="{{ $id }}" @selected(old('author_id', $post->user_id) === $id)>{{ $name }}</option> @endforeach </select> @error('author_id') <div class="text-red-500 mt-2 text-sm"> {{ $message }} </div> @enderror </div> <livewire:post-content-per-language :post="$post"/> <div class="mb-4"> <label for="publish_date" class="sr-only">Published at</label> <input type="datetime-local" name="publish_date" id="publish_date" placeholder="Published at" class="bg-gray-100 border-2 w-full p-4 rounded-lg @error('publish_date') border-red-500 @enderror" value="{{ old('publish_date', $post->publish_date) }}"> @error('publish_date') <div class="text-red-500 mt-2 text-sm"> {{ $message }} </div> @enderror </div> <div> <button type="submit" class="bg-blue-500 text-white px-4 py-3 rounded font-medium w-full"> Update </button> </div></form>
As you can see, we removed the big and ugly foreach
loop and replaced it with a single component:
<livewire:post-content-per-language/> {{-- For create form --}}{{-- And --}}<livewire:post-content-per-language :post="$post"/> {{-- For edit form --}}
Due to the basic implementation of just the switcher with hidden
classes - we don't have to modify our controller at all. We can just use the same code as before!
Opening our create form, we can see that it works as expected:
And the same for the edit form:
While it might not be perfect, it's a great example of how you can use Livewire to create reactive components and handle situations like this.
So, we have looked at how we can add localized models in a simple way, without any packages. However, this might become a bit tedious if we have a lot of Models to translate. In the upcoming lessons, I will show example demos with two packages that may help:
This package allows you to store your translations in a single database table with a JSON-type column. It will store all languages in a single database column. This is a great way to keep your database clean and simple.
This package is different due to its requirement to store translations on a separate database table. It requires more setup initially but provides a lot of flexibility.
Repository: https://github.com/LaravelDaily/laravel11-localization-course/tree/lesson/translating-content-livewire