style updates
This commit is contained in:
@@ -1,24 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Modules\Localization\Models\Locale;
|
||||
use Modules\Localization\Models\LocalizedContent;
|
||||
|
||||
trait HasTranslations
|
||||
{
|
||||
public function translations(): MorphMany
|
||||
{
|
||||
return $this->morphMany(LocalizedContent::class, 'translatable');
|
||||
}
|
||||
|
||||
public function translated(string $field, ?string $locale = null, mixed $fallback = null): mixed
|
||||
{
|
||||
$locale ??= app()->getLocale();
|
||||
$translation = $this->translations->first(fn (LocalizedContent $item): bool => $item->field === $field && $item->locale?->code === $locale);
|
||||
if ($translation && filled($translation->value)) { return $translation->value; }
|
||||
if ($translation && filled($translation->value)) {
|
||||
return $translation->value;
|
||||
}
|
||||
|
||||
return $fallback ?? $this->getAttribute($field);
|
||||
}
|
||||
|
||||
public function setTranslation(string $field, string $localeCode, ?string $value, ?int $userId = null): LocalizedContent
|
||||
{
|
||||
$locale = Locale::query()->where('code', $localeCode)->firstOrFail();
|
||||
|
||||
return $this->translations()->updateOrCreate(
|
||||
['locale_id' => $locale->id, 'field' => $field],
|
||||
['value' => $value, 'updated_by' => $userId]
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Modules\Blog\Models\Blog;
|
||||
use Modules\News\Models\News;
|
||||
use Modules\Pages\Models\Page;
|
||||
|
||||
return [
|
||||
'default_locale' => env('APP_LOCALE', 'en'),
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
'session_key' => 'platform_locale',
|
||||
'supported_subjects' => [
|
||||
'pages' => ['model' => Modules\Pages\Models\Page::class, 'label' => 'Pages', 'fields' => ['title', 'summary', 'content', 'meta_title', 'meta_description']],
|
||||
'blog' => ['model' => Modules\Blog\Models\Blog::class, 'label' => 'Blog', 'fields' => ['title', 'excerpt', 'content', 'meta_title', 'meta_description']],
|
||||
'news' => ['model' => Modules\News\Models\News::class, 'label' => 'News', 'fields' => ['title', 'excerpt', 'content', 'meta_title', 'meta_description']],
|
||||
'pages' => ['model' => Page::class, 'label' => 'Pages', 'fields' => ['title', 'summary', 'content', 'meta_title', 'meta_description']],
|
||||
'blog' => ['model' => Blog::class, 'label' => 'Blog', 'fields' => ['title', 'excerpt', 'content', 'meta_title', 'meta_description']],
|
||||
'news' => ['model' => News::class, 'label' => 'News', 'fields' => ['title', 'excerpt', 'content', 'meta_title', 'meta_description']],
|
||||
],
|
||||
];
|
||||
|
||||
+3
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
@@ -35,6 +37,7 @@ return new class extends Migration
|
||||
$table->index(['locale_id', 'field']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('localized_contents');
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Modules\Localization\Models\Locale;
|
||||
|
||||
final class LocaleSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Modules\Localization\Models\Locale;
|
||||
use Modules\Localization\Services\LocaleManager;
|
||||
|
||||
final class LocaleController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
@@ -13,48 +16,63 @@ final class LocaleController extends Controller
|
||||
abort_unless(auth()->user()->can('localization.manage'), 403);
|
||||
$subjects = collect(config('localization.supported_subjects'))->map(function (array $config): array {
|
||||
$model = $config['model'];
|
||||
|
||||
return $config + ['records' => $model::query()->latest()->limit(8)->get()];
|
||||
});
|
||||
|
||||
return view('localization::admin.index', ['locales' => Locale::query()->orderBy('position')->get(), 'subjects' => $subjects]);
|
||||
}
|
||||
|
||||
public function switch(string $locale, LocaleManager $manager): RedirectResponse
|
||||
{
|
||||
$manager->activate($locale);
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->can('localization.manage'), 403);
|
||||
$data = $request->validate($this->rules());
|
||||
if (($data['is_default'] ?? false) === true) { Locale::query()->update(['is_default' => false]); }
|
||||
if (($data['is_default'] ?? false) === true) {
|
||||
Locale::query()->update(['is_default' => false]);
|
||||
}
|
||||
Locale::query()->create($data);
|
||||
|
||||
return back()->with('success', 'Locale created.');
|
||||
}
|
||||
|
||||
public function update(Request $request, Locale $locale): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->can('localization.manage'), 403);
|
||||
$data = $request->validate($this->rules($locale));
|
||||
if (($data['is_default'] ?? false) === true) { Locale::query()->whereKeyNot($locale->id)->update(['is_default' => false]); }
|
||||
if (($data['is_default'] ?? false) === true) {
|
||||
Locale::query()->whereKeyNot($locale->id)->update(['is_default' => false]);
|
||||
}
|
||||
$locale->update($data);
|
||||
|
||||
return back()->with('success', 'Locale updated.');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Locale $locale): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->can('localization.manage'), 403);
|
||||
abort_if($locale->is_default, 422, 'The default locale cannot be deleted.');
|
||||
$locale->delete();
|
||||
|
||||
return back()->with('success', 'Locale deleted.');
|
||||
}
|
||||
|
||||
private function rules(?Locale $locale = null): array
|
||||
{
|
||||
return [
|
||||
'code' => ['required','string','max:20','unique:locales,code,'.($locale?->id ?? 'NULL')],
|
||||
'name' => ['required','string','max:100'], 'native_name' => ['required','string','max:100'],
|
||||
'regional_code' => ['nullable','string','max:20'], 'direction' => ['required','in:ltr,rtl'],
|
||||
'date_format' => ['required','string','max:40'], 'time_format' => ['required','string','max:40'],
|
||||
'timezone' => ['required','timezone'], 'currency_code' => ['nullable','string','size:3'],
|
||||
'currency_symbol' => ['nullable','string','max:10'], 'position' => ['nullable','integer','min:0'],
|
||||
'is_default' => ['sometimes','boolean'], 'is_active' => ['sometimes','boolean'],
|
||||
'code' => ['required', 'string', 'max:20', 'unique:locales,code,'.($locale?->id ?? 'NULL')],
|
||||
'name' => ['required', 'string', 'max:100'], 'native_name' => ['required', 'string', 'max:100'],
|
||||
'regional_code' => ['nullable', 'string', 'max:20'], 'direction' => ['required', 'in:ltr,rtl'],
|
||||
'date_format' => ['required', 'string', 'max:40'], 'time_format' => ['required', 'string', 'max:40'],
|
||||
'timezone' => ['required', 'timezone'], 'currency_code' => ['nullable', 'string', 'size:3'],
|
||||
'currency_symbol' => ['nullable', 'string', 'max:10'], 'position' => ['nullable', 'integer', 'min:0'],
|
||||
'is_default' => ['sometimes', 'boolean'], 'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Modules\Localization\Models\Locale;
|
||||
|
||||
final class TranslationController extends Controller
|
||||
{
|
||||
public function edit(string $subject, int $id): View
|
||||
@@ -12,8 +15,10 @@ final class TranslationController extends Controller
|
||||
abort_unless(auth()->user()->can('localization.translate'), 403);
|
||||
[$record, $config] = $this->resolve($subject, $id);
|
||||
$record->load('translations.locale');
|
||||
|
||||
return view('localization::admin.translate', ['record' => $record, 'subject' => $subject, 'config' => $config, 'locales' => Locale::query()->active()->get()]);
|
||||
}
|
||||
|
||||
public function update(Request $request, string $subject, int $id): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->can('localization.translate'), 403);
|
||||
@@ -24,13 +29,16 @@ final class TranslationController extends Controller
|
||||
$record->setTranslation($field, $localeCode, $fields[$field] ?? null, $request->user()->id);
|
||||
}
|
||||
}
|
||||
|
||||
return back()->with('success', 'Translations saved.');
|
||||
}
|
||||
|
||||
private function resolve(string $subject, int $id): array
|
||||
{
|
||||
$config = config("localization.supported_subjects.$subject");
|
||||
abort_unless(is_array($config), 404);
|
||||
$record = $config['model']::query()->findOrFail($id);
|
||||
|
||||
return [$record, $config];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class Locale extends Model
|
||||
{
|
||||
protected $fillable = ['code','name','native_name','regional_code','direction','date_format','time_format','timezone','currency_code','currency_symbol','position','is_default','is_active'];
|
||||
protected function casts(): array { return ['is_default' => 'boolean', 'is_active' => 'boolean']; }
|
||||
public function translations(): HasMany { return $this->hasMany(LocalizedContent::class); }
|
||||
public function scopeActive(Builder $query): Builder { return $query->where('is_active', true)->orderBy('position')->orderBy('name'); }
|
||||
protected $fillable = ['code', 'name', 'native_name', 'regional_code', 'direction', 'date_format', 'time_format', 'timezone', 'currency_code', 'currency_symbol', 'position', 'is_default', 'is_active'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['is_default' => 'boolean', 'is_active' => 'boolean'];
|
||||
}
|
||||
|
||||
public function translations(): HasMany
|
||||
{
|
||||
return $this->hasMany(LocalizedContent::class);
|
||||
}
|
||||
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_active', true)->orderBy('position')->orderBy('name');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
final class LocalizedContent extends Model
|
||||
{
|
||||
protected $fillable = ['locale_id','field','value','updated_by'];
|
||||
public function translatable(): MorphTo { return $this->morphTo(); }
|
||||
public function locale(): BelongsTo { return $this->belongsTo(Locale::class); }
|
||||
public function editor(): BelongsTo { return $this->belongsTo(User::class, 'updated_by'); }
|
||||
protected $fillable = ['locale_id', 'field', 'value', 'updated_by'];
|
||||
|
||||
public function translatable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function locale(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Locale::class);
|
||||
}
|
||||
|
||||
public function editor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Localization\Services\LocaleManager;
|
||||
|
||||
final class LocalizationServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
@@ -10,6 +13,7 @@ final class LocalizationServiceProvider extends ServiceProvider
|
||||
$this->app->singleton(LocaleManager::class);
|
||||
$this->app->register(RouteServiceProvider::class);
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(module_path('Localization', 'Database/Migrations'));
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
final class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Localization\Http\Controllers\LocaleController;
|
||||
use Modules\Localization\Http\Controllers\TranslationController;
|
||||
|
||||
Route::get('/locale/{locale}', [LocaleController::class, 'switch'])->name('localization.switch');
|
||||
Route::middleware(['auth','verified'])->prefix('administration/localization')->name('administration.localization.')->group(function (): void {
|
||||
Route::middleware(['auth', 'verified'])->prefix('administration/localization')->name('administration.localization.')->group(function (): void {
|
||||
Route::get('/', [LocaleController::class, 'index'])->name('index');
|
||||
Route::post('/locales', [LocaleController::class, 'store'])->name('locales.store');
|
||||
Route::put('/locales/{locale}', [LocaleController::class, 'update'])->name('locales.update');
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Services;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Modules\Localization\Models\Locale;
|
||||
|
||||
final class LocaleManager
|
||||
{
|
||||
public function active(): Collection { return Locale::query()->active()->get(); }
|
||||
public function default(): ?Locale { return Locale::query()->where('is_default', true)->first() ?? Locale::query()->active()->first(); }
|
||||
public function active(): Collection
|
||||
{
|
||||
return Locale::query()->active()->get();
|
||||
}
|
||||
|
||||
public function default(): ?Locale
|
||||
{
|
||||
return Locale::query()->where('is_default', true)->first() ?? Locale::query()->active()->first();
|
||||
}
|
||||
|
||||
public function activate(string $code): Locale
|
||||
{
|
||||
$locale = Locale::query()->active()->where('code', $code)->firstOrFail();
|
||||
app()->setLocale($locale->code);
|
||||
session()->put(config('localization.session_key'), $locale->code);
|
||||
|
||||
return $locale;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Localization\Tests\Feature;
|
||||
|
||||
use Modules\Localization\Providers\LocalizationServiceProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class ModuleBootTest extends TestCase
|
||||
{
|
||||
public function test_localization_module_is_registered(): void
|
||||
{
|
||||
$this->assertTrue(app()->providerIsLoaded(\Modules\Localization\Providers\LocalizationServiceProvider::class));
|
||||
$this->assertTrue(app()->providerIsLoaded(LocalizationServiceProvider::class));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user