Translation hasMany relation: how to populate form and field by locale

I have to a model: Category, it has a relation CategoryI10n which has a field: locale. In the Category model I defined the relation:
public function translations(){
return $this->hasMany(CategoryI10n::class);
}
public function translations(){
return $this->hasMany(CategoryI10n::class);
}
I iterate through the available locales, create tabs, and text fields: TextInput::make("name_$locale") Then in the CreateCategory.php I implemented a custom handleRecordCreation function, which creates the necessary records in the relation. But I have no clue, how to display only one translation record corresponds to the current record: - if I add TextColumn::make("translations.name"), it displays all the 3 available translations. - I tried to write a __get() function in Category model, if the property is name_en, name_fr it query the relation and give back the correct value. It works well in the tinker, but for some reason the TextColumn::make("name_en") does not call the __get() method. Can you help me here how would you handle this problem? Thank you in advance!
8 Replies
John Wink
John Wink7mo ago
In your case it would be very helpful if you had a demo repo. This way it is very difficult to understand what it should do and what it is currently doing
Roland Barkóczi
Roland BarkócziOP7mo ago
Here is the migration file:
Schema::create('m07_categories', function (Blueprint $table) {
$table->id();
$table->foreignId('created_by')->nullable()->constrained(table: 'users')->nullOnDelete();
$table->boolean('is_public')->index()->default(true);
$table->softDeletes();
$table->timestamps();
});

Schema::create('m07_category_i10n', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained('m07_categories')->cascadeOnDelete();
$table->string('locale');
$table->string('slug')->unique()->index();
$table->string('name');
$table->text('description')->nullable();
$table->unique(['category_id', 'locale']);
});
Schema::create('m07_categories', function (Blueprint $table) {
$table->id();
$table->foreignId('created_by')->nullable()->constrained(table: 'users')->nullOnDelete();
$table->boolean('is_public')->index()->default(true);
$table->softDeletes();
$table->timestamps();
});

Schema::create('m07_category_i10n', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained('m07_categories')->cascadeOnDelete();
$table->string('locale');
$table->string('slug')->unique()->index();
$table->string('name');
$table->text('description')->nullable();
$table->unique(['category_id', 'locale']);
});
Roland Barkóczi
Roland BarkócziOP7mo ago
I want to display here only one related record which is corresponding to the current local
No description
Roland Barkóczi
Roland BarkócziOP7mo ago
I also have no clue, how to populate the form when I am editing a record I wrote a getter function in the Category model:
public function __get($key)
{
$pattern = '/^(name|description|slug)_(\w{2})$/';
if (preg_match($pattern, $key, $matches)) {
$field = $matches[1];
$locale = $matches[2];
return $this->translations()->where('locale', $locale)->first()->$field ?? null;
}
return parent::__get($key);
}
public function __get($key)
{
$pattern = '/^(name|description|slug)_(\w{2})$/';
if (preg_match($pattern, $key, $matches)) {
$field = $matches[1];
$locale = $matches[2];
return $this->translations()->where('locale', $locale)->first()->$field ?? null;
}
return parent::__get($key);
}
I expocted when I use the 'name_en' in the TextColumn::make function, it will call this function (as name_en is not a data field in the schema)
John Wink
John Wink7mo ago
Maybe something like this:
protected function mutateFormDataBeforeFill(array $data): array
{
$record = $this->record->loadMissing(‘translations’);

// Load required translations into $data
// $data[‘name’] = $record->name_en;
// Dynamic locale:
// $field = 'name_'.$locale;
// $data[‘name’] = $record->$field;

return parent::mutateFormDataBeforeFill($data);
}
protected function mutateFormDataBeforeFill(array $data): array
{
$record = $this->record->loadMissing(‘translations’);

// Load required translations into $data
// $data[‘name’] = $record->name_en;
// Dynamic locale:
// $field = 'name_'.$locale;
// $data[‘name’] = $record->$field;

return parent::mutateFormDataBeforeFill($data);
}
In the edit page of the respective resource? Otherwise via ->fillForm() if you call it via an action?
Roland Barkóczi
Roland BarkócziOP7mo ago
Hello CyQuer! Thank you very much for your help! After re-reading the documentation, finally I managed to implement as I wanted.
Roland Barkóczi
Roland BarkócziOP7mo ago
No description
Roland Barkóczi
Roland BarkócziOP7mo ago
the biggest problem was I did not know exactly what I wanted but the ListCategories -> getTabs() function was the solution
public function getTabs(): array
{
$locales = config('app.available_locales');
$localeNamesAndFlags = config('locales');
$tabs = [];
foreach ($locales as $locale) {
$tabs[] = Tab::make()
->label($localeNamesAndFlags[$locale]['flag'] . ' ' . __($localeNamesAndFlags[$locale]['name']))
->modifyQueryUsing(
fn (Builder $query) => $query->with(
'translations',
fn ($q) => $q->where('locale', $locale)
)
);
}
return $tabs;
}
public function getTabs(): array
{
$locales = config('app.available_locales');
$localeNamesAndFlags = config('locales');
$tabs = [];
foreach ($locales as $locale) {
$tabs[] = Tab::make()
->label($localeNamesAndFlags[$locale]['flag'] . ' ' . __($localeNamesAndFlags[$locale]['name']))
->modifyQueryUsing(
fn (Builder $query) => $query->with(
'translations',
fn ($q) => $q->where('locale', $locale)
)
);
}
return $tabs;
}
Want results from more Discord servers?
Add your server