Implement Translation Tabs Component: Added TranslationTabs component for managing translations in Blog and Page resources. Integrated translation fields into forms, ensuring localization support for titles, content, and metadata. Updated existing pages to save and load translations, enhancing the universal translation system. Added relevant localization keys for English and Turkish.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Admin\Resources\Blogs\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Blogs\BlogResource;
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateBlog extends CreateRecord
|
||||
@@ -23,4 +24,10 @@ class CreateBlog extends CreateRecord
|
||||
{
|
||||
return __('blog.created_successfully');
|
||||
}
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
// Save translations
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Admin\Resources\Blogs\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Blogs\BlogResource;
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
@@ -38,4 +39,28 @@ class EditBlog extends EditRecord
|
||||
{
|
||||
return __('blog.updated_successfully');
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
// Load existing translations
|
||||
$translationData = TranslationTabs::fillFromRecord($this->record);
|
||||
|
||||
\Log::info('Loading translations for edit', [
|
||||
'blog_id' => $this->record->id,
|
||||
'translations' => $translationData
|
||||
]);
|
||||
|
||||
return array_merge($data, $translationData);
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
\Log::info('Saving blog translations', [
|
||||
'blog_id' => $this->record->id,
|
||||
'form_state' => $this->form->getState()
|
||||
]);
|
||||
|
||||
// Save translations
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Blogs\Schemas;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
@@ -149,6 +150,47 @@ class BlogForm
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true),
|
||||
|
||||
// Çeviri Sekmesi
|
||||
Section::make('🌍 ' . __('blog.translations_section'))
|
||||
->schema([
|
||||
TranslationTabs::make([
|
||||
'title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('blog.title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'content' => [
|
||||
'type' => 'richtext',
|
||||
'label' => __('blog.content_field'),
|
||||
'required' => false,
|
||||
],
|
||||
'excerpt' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('blog.excerpt_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 500,
|
||||
'rows' => 3,
|
||||
],
|
||||
'meta_title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('blog.meta_title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 60,
|
||||
],
|
||||
'meta_description' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('blog.meta_description_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 160,
|
||||
'rows' => 3,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(false),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Components;
|
||||
|
||||
use App\Models\Language;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
|
||||
class TranslationTabs
|
||||
{
|
||||
public static function make(array $fields = []): Tabs
|
||||
{
|
||||
$languages = Language::active()->ordered()->get();
|
||||
|
||||
if ($languages->isEmpty()) {
|
||||
return Tabs::make('translations');
|
||||
}
|
||||
|
||||
$tabs = [];
|
||||
|
||||
foreach ($languages as $language) {
|
||||
// User permission kontrolü
|
||||
$canManage = auth()->user()?->canManageLanguage($language->code) ?? true;
|
||||
|
||||
if (!$canManage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabFields = [];
|
||||
|
||||
foreach ($fields as $fieldName => $fieldConfig) {
|
||||
$fieldType = $fieldConfig['type'] ?? 'text';
|
||||
$label = $fieldConfig['label'] ?? ucfirst($fieldName);
|
||||
$required = $fieldConfig['required'] ?? false;
|
||||
$maxLength = $fieldConfig['maxLength'] ?? null;
|
||||
$rows = $fieldConfig['rows'] ?? 3;
|
||||
|
||||
$fieldKey = "translations.{$language->code}.{$fieldName}";
|
||||
|
||||
switch ($fieldType) {
|
||||
case 'richtext':
|
||||
$field = RichEditor::make($fieldKey)
|
||||
->label($label)
|
||||
->fileAttachmentsDisk('public')
|
||||
->fileAttachmentsDirectory('translations')
|
||||
->fileAttachmentsVisibility('public')
|
||||
->toolbarButtons([
|
||||
'attachFiles',
|
||||
'blockquote',
|
||||
'bold',
|
||||
'bulletList',
|
||||
'codeBlock',
|
||||
'h2',
|
||||
'h3',
|
||||
'italic',
|
||||
'link',
|
||||
'orderedList',
|
||||
'redo',
|
||||
'strike',
|
||||
'underline',
|
||||
'undo',
|
||||
])
|
||||
->columnSpanFull()
|
||||
->required($required);
|
||||
break;
|
||||
|
||||
case 'textarea':
|
||||
$field = Textarea::make($fieldKey)
|
||||
->label($label)
|
||||
->rows($rows)
|
||||
->columnSpanFull();
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
default:
|
||||
$field = TextInput::make($fieldKey)
|
||||
->label($label);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($required) {
|
||||
$field->required();
|
||||
}
|
||||
|
||||
if ($maxLength) {
|
||||
$field->maxLength($maxLength);
|
||||
}
|
||||
|
||||
$tabFields[] = $field;
|
||||
}
|
||||
|
||||
// Convert country code to flag emoji
|
||||
$code = strtoupper($language->code);
|
||||
$flag = '';
|
||||
for ($i = 0; $i < strlen($code); $i++) {
|
||||
$flag .= mb_chr(127397 + ord($code[$i]));
|
||||
}
|
||||
|
||||
$tabs[] = Tab::make($language->name)
|
||||
->label($flag . ' ' . $language->native_name)
|
||||
->schema($tabFields)
|
||||
->badge(fn ($record) => $record
|
||||
? self::getTranslationBadge($record, $language->code)
|
||||
: null
|
||||
)
|
||||
->badgeColor(fn ($record) => $record
|
||||
? self::getTranslationBadgeColor($record, $language->code)
|
||||
: 'gray'
|
||||
);
|
||||
}
|
||||
|
||||
return Tabs::make('translations')
|
||||
->tabs($tabs)
|
||||
->columnSpanFull()
|
||||
->persistTabInQueryString();
|
||||
}
|
||||
|
||||
protected static function getTranslationBadge($record, string $languageCode): ?string
|
||||
{
|
||||
if (!method_exists($record, 'getTranslationProgress')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$progress = $record->getTranslationProgress();
|
||||
$percentage = $progress[$languageCode] ?? 0;
|
||||
|
||||
if ($percentage === 100) {
|
||||
return '✓';
|
||||
} elseif ($percentage > 0) {
|
||||
return $percentage . '%';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static function getTranslationBadgeColor($record, string $languageCode): string
|
||||
{
|
||||
if (!method_exists($record, 'getTranslationProgress')) {
|
||||
return 'gray';
|
||||
}
|
||||
|
||||
$progress = $record->getTranslationProgress();
|
||||
$percentage = $progress[$languageCode] ?? 0;
|
||||
|
||||
if ($percentage === 100) {
|
||||
return 'success';
|
||||
} elseif ($percentage >= 50) {
|
||||
return 'warning';
|
||||
} elseif ($percentage > 0) {
|
||||
return 'danger';
|
||||
}
|
||||
|
||||
return 'gray';
|
||||
}
|
||||
|
||||
public static function fillFromRecord($record): array
|
||||
{
|
||||
$data = ['translations' => []];
|
||||
|
||||
if (!method_exists($record, 'getTranslatableFields')) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$languages = Language::active()->ordered()->get();
|
||||
$fields = $record->getTranslatableFields();
|
||||
|
||||
foreach ($languages as $language) {
|
||||
$data['translations'][$language->code] = [];
|
||||
foreach ($fields as $field) {
|
||||
$translation = $record->getTranslation($field, $language->code, false);
|
||||
if ($translation) {
|
||||
$data['translations'][$language->code][$field] = $translation->field_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function saveTranslations($record, array $data): void
|
||||
{
|
||||
if (!method_exists($record, 'setTranslation')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$translations = $data['translations'] ?? [];
|
||||
|
||||
\Log::info('Saving translations', ['data' => $translations]);
|
||||
|
||||
foreach ($translations as $languageCode => $fields) {
|
||||
if (!is_array($fields)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fields as $fieldName => $value) {
|
||||
// Boş değerleri de kaydet (silme işlemi için)
|
||||
$record->setTranslation(
|
||||
$fieldName,
|
||||
$languageCode,
|
||||
$value ?? '',
|
||||
'published', // Default status
|
||||
auth()->id()
|
||||
);
|
||||
|
||||
\Log::info('Translation saved', [
|
||||
'language' => $languageCode,
|
||||
'field' => $fieldName,
|
||||
'value' => substr($value ?? '', 0, 50)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use App\Filament\Admin\Resources\Pages\PageResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
@@ -23,4 +24,10 @@ class CreatePage extends CreateRecord
|
||||
{
|
||||
return __('pages.created_successfully');
|
||||
}
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
// Save translations
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use App\Filament\Admin\Resources\Pages\PageResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
@@ -38,4 +39,18 @@ class EditPage extends EditRecord
|
||||
{
|
||||
return __('pages.updated_successfully');
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
// Load existing translations
|
||||
$translationData = TranslationTabs::fillFromRecord($this->record);
|
||||
|
||||
return array_merge($data, $translationData);
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
// Save translations
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
@@ -162,6 +163,47 @@ class PageForm
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true),
|
||||
|
||||
// Çeviri Sekmesi
|
||||
Section::make('🌍 ' . __('pages.translations_section'))
|
||||
->schema([
|
||||
TranslationTabs::make([
|
||||
'title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('pages.title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'content' => [
|
||||
'type' => 'richtext',
|
||||
'label' => __('pages.content_field'),
|
||||
'required' => false,
|
||||
],
|
||||
'excerpt' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('pages.excerpt_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 500,
|
||||
'rows' => 3,
|
||||
],
|
||||
'meta_title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('pages.meta_title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 60,
|
||||
],
|
||||
'meta_description' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('pages.meta_description_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 160,
|
||||
'rows' => 3,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(false),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user