Add Site Translation Management: Created SiteTranslation resource with pages for listing, creating, and editing translations. Implemented localization support for translation keys and values, enhancing the admin panel's usability for managing site translations. Introduced a new migration for the site_translations table and updated helper functions for translation retrieval.

This commit is contained in:
Ümit Tunç
2025-12-30 23:41:32 +03:00
parent f381caaa39
commit 064ec327ba
13 changed files with 376 additions and 252 deletions
@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Admin\Resources\SiteTranslations\Pages;
use App\Filament\Admin\Resources\SiteTranslations\SiteTranslationResource;
use Filament\Resources\Pages\CreateRecord;
class CreateSiteTranslation extends CreateRecord
{
protected static string $resource = SiteTranslationResource::class;
}
@@ -0,0 +1,20 @@
<?php
namespace App\Filament\Admin\Resources\SiteTranslations\Pages;
use App\Filament\Admin\Resources\SiteTranslations\SiteTranslationResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditSiteTranslation extends EditRecord
{
protected static string $resource = SiteTranslationResource::class;
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Filament\Admin\Resources\SiteTranslations\Pages;
use App\Filament\Admin\Resources\SiteTranslations\SiteTranslationResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListSiteTranslations extends ListRecords
{
protected static string $resource = SiteTranslationResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -0,0 +1,44 @@
<?php
namespace App\Filament\Admin\Resources\SiteTranslations\Schemas;
use App\Models\Language;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Group;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class SiteTranslationForm
{
public static function make(Schema $schema): Schema
{
$languages = Language::where('is_active', true)->get();
$translationFields = [];
foreach ($languages as $language) {
$translationFields[] = Textarea::make($language->code)
->label($language->name . " ({$language->code})")
->rows(2);
}
return $schema
->components([
Section::make()
->schema([
Textarea::make('key')
->label('Key / Original Text')
->required()
->columnSpanFull()
->helperText('The original text to be translated. Use this exact text in t() helper.'),
Group::make()
->schema($translationFields)
->statePath('translations')
->columnSpanFull(),
])
]);
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Filament\Admin\Resources\SiteTranslations;
use App\Filament\Admin\Resources\SiteTranslations\Pages;
use App\Filament\Admin\Resources\SiteTranslations\Schemas\SiteTranslationForm;
use App\Filament\Admin\Resources\SiteTranslations\Tables\SiteTranslationsTable;
use App\Models\SiteTranslation;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
use UnitEnum;
use BackedEnum;
class SiteTranslationResource extends Resource
{
protected static ?string $model = SiteTranslation::class;
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-language';
protected static UnitEnum|string|null $navigationGroup = 'Settings';
protected static ?int $navigationSort = 100;
public static function form(Schema $schema): Schema
{
return SiteTranslationForm::make($schema);
}
public static function table(Table $table): Table
{
return SiteTranslationsTable::make($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSiteTranslations::route('/'),
'create' => Pages\CreateSiteTranslation::route('/create'),
'edit' => Pages\EditSiteTranslation::route('/{record}/edit'),
];
}
public static function getNavigationLabel(): string
{
return __('Site Translations');
}
public static function getModelLabel(): string
{
return __('Translation');
}
public static function getPluralModelLabel(): string
{
return __('Translations');
}
}
@@ -0,0 +1,52 @@
<?php
namespace App\Filament\Admin\Resources\SiteTranslations\Tables;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class SiteTranslationsTable
{
public static function make(Table $table): Table
{
$defaultLocale = app()->getLocale();
return $table
->columns([
TextColumn::make('key')
->searchable()
->sortable()
->limit(50)
->wrap(),
TextColumn::make("translations.{$defaultLocale}")
->label("Translation ({$defaultLocale})")
->searchable()
->limit(50)
->wrap()
->placeholder('No translation'),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
}
+32 -2
View File
@@ -1,6 +1,8 @@
<?php
use App\Models\Setting;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
if (!function_exists('setting')) {
/**
@@ -12,7 +14,35 @@ if (!function_exists('setting')) {
*/
function setting(string $key, $default = null)
{
return Setting::get($key, $default);
// Cache settings to avoid database queries on every call
// Cache will store the full setting object to access type info
$setting = Cache::remember("app_setting_{$key}", 60 * 60 * 24, function () use ($key) {
return Setting::where('key', $key)
->where('is_active', true)
->first();
});
if (!$setting) {
return $default;
}
$value = $setting->value;
// Handle file/image types
if (in_array($setting->type, ['file', 'image']) || $key === 'home_hero') {
if (empty($value)) {
return $default;
}
// If value is already a URL, return it
if (filter_var($value, FILTER_VALIDATE_URL)) {
return $value;
}
// Return storage URL
return Storage::url($value);
}
return $value;
}
}
+17
View File
@@ -2,6 +2,7 @@
use App\Models\Language;
use App\Models\Translation;
use App\Models\SiteTranslation;
use Illuminate\Support\Collection;
if (!function_exists('current_language')) {
@@ -279,3 +280,19 @@ if (!function_exists('get_translation_value')) {
}
}
if (!function_exists('t')) {
/**
* Get site translation for a given key.
*
* @param string $key
* @return string
*/
function t(string $key): string
{
if (empty($key)) {
return '';
}
return SiteTranslation::getTranslation($key);
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
class SiteTranslation extends Model
{
use HasFactory;
protected $fillable = [
'hash',
'key',
'translations',
];
protected $casts = [
'translations' => 'array',
];
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
if (empty($model->hash)) {
$model->hash = md5($model->key);
}
});
static::updating(function ($model) {
// Key değiştiyse hash'i güncelle
if ($model->isDirty('key')) {
$model->hash = md5($model->key);
}
});
static::saved(function ($model) {
Cache::forget("site_translation_{$model->hash}");
});
}
/**
* Get translation for a specific key
*/
public static function getTranslation(string $key): string
{
$hash = md5($key);
$locale = app()->getLocale();
// Cache'ten veya DB'den al
// Cache süresi: 24 saat
$translation = Cache::remember("site_translation_{$hash}", 60 * 60 * 24, function () use ($hash, $key) {
return static::firstOrCreate(
['hash' => $hash],
['key' => $key]
);
});
// Eğer kayıt yeni oluşturulduysa ve key eksikse (concurrency nedeniyle), key'i set et
if ($translation->wasRecentlyCreated && empty($translation->translations)) {
// Yeni oluşturuldu, henüz çeviri yok.
// Varsayılan olarak key'i döndür.
return $key;
}
$translations = $translation->translations ?? [];
if (isset($translations[$locale]) && !empty($translations[$locale])) {
return $translations[$locale];
}
return $key;
}
}