feat(i18n): Complete localization of marketing frontend with react-i18next, prefixed URLs, JSON migrations, and automation

This commit is contained in:
Codex Agent
2025-10-03 13:05:13 +02:00
parent 1845d83583
commit 60f8de9162
46 changed files with 3454 additions and 590 deletions

View File

@@ -97,39 +97,36 @@ use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;
// Event types (global)
Schema::create('event_types', function (Blueprint $table) {
$table->id();
$table->json('name');
$table->json('name'); // Translatable: { "de": "Hochzeit", "en": "Wedding" }
$table->string('slug', 100)->unique();
$table->string('icon', 64)->nullable();
$table->json('settings')->nullable();
$table->timestamps();
});
// Events (tenant-scoped)
Schema::create('events', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->json('name');
$table->json('name'); // Translatable: { "de": "Event Name", "en": "Event Name" }
$table->date('date');
$table->string('slug');
$table->json('description')->nullable();
$table->json('description')->nullable(); // Translatable
$table->json('settings')->nullable();
$table->foreignId('event_type_id')->constrained('event_types');
$table->boolean('is_active')->default(true);
$table->string('default_locale', 5)->default('de');
$table->string('default_locale', 5)->default('de'); // For event-specific i18n fallback
$table->timestamps();
$table->unique(['tenant_id', 'slug']);
});
// Emotions (global library)
Schema::create('emotions', function (Blueprint $table) {
$table->id();
$table->json('name');
$table->json('name'); // Translatable: { "de": "Freude", "en": "Joy" }
$table->string('icon', 50);
$table->string('color', 7);
$table->json('description')->nullable();
$table->json('description')->nullable(); // Translatable
$table->integer('sort_order')->default(0);
$table->boolean('is_active')->default(true);
});
@@ -141,22 +138,21 @@ Schema::create('emotion_event_type', function (Blueprint $table) {
$table->primary(['emotion_id', 'event_type_id']);
});
// Tasks (with optional tenant/event scoping)
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->foreignId('emotion_id')->constrained('emotions');
$table->foreignId('event_type_id')->nullable()->constrained('event_types')->nullOnDelete();
$table->json('title');
$table->json('description');
$table->json('title'); // Translatable
$table->json('description'); // Translatable
$table->string('difficulty', 16)->default('easy'); // app enum
$table->json('example_text')->nullable();
$table->json('example_text')->nullable(); // Translatable
$table->integer('sort_order')->default(0);
$table->boolean('is_active')->default(true);
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->nullOnDelete();
$table->string('scope', 16)->default('global'); // global|tenant|event
$table->foreignId('event_id')->nullable()->constrained('events')->nullOnDelete();
$table->timestamps();
$table->unique(['tenant_id', 'emotion_id', 'title']);
$table->unique(['tenant_id', 'emotion_id', 'title->de']); // Example for de fallback; adjust for multi-locale
});
// Photos
@@ -196,8 +192,8 @@ Schema::create('legal_pages', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->nullOnDelete();
$table->string('slug', 32); // imprint|privacy|terms|custom
$table->json('title');
$table->json('body_markdown');
$table->json('title'); // Translatable
$table->json('body_markdown'); // Translatable Markdown content per locale
$table->string('locale_fallback', 5)->default('de');
$table->unsignedInteger('version')->default(1);
$table->timestamp('effective_from')->nullable();
@@ -211,3 +207,4 @@ Schema::create('legal_pages', function (Blueprint $table) {
- Prefer app-level enums (string columns + validation) over DB `ENUM`.
- Use `cascadeOnDelete()` only where child data must be removed with parent; otherwise `nullOnDelete()`.
- Every tenant-owned table should include `tenant_id` and appropriate composite indexes.
- **i18n Integration**: JSON fields (e.g., `name`, `description`) store locale-specific values as `{ "de": "Text", "en": "Text" }`. Use Laravel's `json` cast or spatie/laravel-translatable for access. Fallback to `default_locale` or global fallback ('de'). Update via Filament resources with locale selectors. Ensure indexes on JSON paths if querying (e.g., `->de` for German titles).