From db7ce880a0d6cb531a1d0b087e085b9208810178 Mon Sep 17 00:00:00 2001 From: parsa aghaei Date: Tue, 23 Jun 2026 18:31:51 +0330 Subject: [PATCH] 2.1-2.4: install spatie/laravel-translatable, migrate to JSON, update models & controllers --- .../Api/Website/AboutMeController.php | 23 +- .../Api/Website/ProjectController.php | 48 ++-- .../Api/Website/ServiceController.php | 16 +- app/Models/Blog/Category.php | 2 +- app/Models/Website/AboutMe.php | 40 +-- app/Models/Website/AboutMeTranslation.php | 22 -- app/Models/Website/Project.php | 10 +- app/Models/Website/ProjectCategory.php | 10 +- .../Website/ProjectCategoryTranslation.php | 14 - app/Models/Website/ProjectTranslation.php | 19 -- app/Models/Website/Service.php | 44 +--- app/Models/Website/ServiceTranslation.php | 22 -- app/Models/Website/Skill.php | 49 +--- app/Models/Website/SkillTranslation.php | 22 -- composer.json | 3 +- composer.lock | 146 ++++++++++- ..._145429_migrate_to_spatie_translatable.php | 248 ++++++++++++++++++ 17 files changed, 453 insertions(+), 285 deletions(-) delete mode 100644 app/Models/Website/AboutMeTranslation.php delete mode 100644 app/Models/Website/ProjectCategoryTranslation.php delete mode 100644 app/Models/Website/ProjectTranslation.php delete mode 100644 app/Models/Website/ServiceTranslation.php delete mode 100644 app/Models/Website/SkillTranslation.php create mode 100644 database/migrations/2026_06_23_145429_migrate_to_spatie_translatable.php diff --git a/app/Http/Controllers/Api/Website/AboutMeController.php b/app/Http/Controllers/Api/Website/AboutMeController.php index 95e099c..c727339 100644 --- a/app/Http/Controllers/Api/Website/AboutMeController.php +++ b/app/Http/Controllers/Api/Website/AboutMeController.php @@ -11,13 +11,13 @@ class AboutMeController extends Controller { public function index() { - $aboutMe = AboutMe::with('skills.translations')->first(); + $aboutMe = AboutMe::with('skills')->first(); return response()->json($aboutMe); } public function show($id) { - $aboutMe = AboutMe::with('skills.translations')->findOrFail($id); + $aboutMe = AboutMe::with('skills')->findOrFail($id); return response()->json($aboutMe); } @@ -42,24 +42,17 @@ public function destroy($id) public function getTranslation($id, $locale) { - $aboutMe = AboutMe::with(['translations' => function ($query) use ($locale) { - $query->where('locale', $locale); - }, 'skills.translations' => function ($query) use ($locale) { - $query->where('locale', $locale); - }])->findOrFail($id); + $aboutMe = AboutMe::with('skills')->findOrFail($id); - if ($aboutMe->translations->isEmpty()) { + if (!$aboutMe->hasTranslation('title', $locale)) { return response()->json(['message' => 'Translation not found', 'locale' => $locale], 404); } - $translation = $aboutMe->translations->first(); $skills = $aboutMe->skills->map(function ($skill) use ($locale) { - $translation = $skill->getTranslation($locale); - return [ 'id' => $skill->id, - 'title' => $translation ? $translation->title : null, - 'description' => $translation ? $translation->description : null, + 'title' => $skill->getTranslation('title', $locale), + 'description' => $skill->getTranslation('description', $locale), 'image' => $skill->image, 'link' => $skill->link, 'percentage' => $skill->percentage, @@ -68,8 +61,8 @@ public function getTranslation($id, $locale) $data = [ 'id' => $aboutMe->id, - 'title' => $translation->title, - 'description' => $translation->description, + 'title' => $aboutMe->getTranslation('title', $locale), + 'description' => $aboutMe->getTranslation('description', $locale), 'image' => $aboutMe->image, 'skills' => $skills, ]; diff --git a/app/Http/Controllers/Api/Website/ProjectController.php b/app/Http/Controllers/Api/Website/ProjectController.php index 5b96981..37abfd0 100644 --- a/app/Http/Controllers/Api/Website/ProjectController.php +++ b/app/Http/Controllers/Api/Website/ProjectController.php @@ -14,17 +14,14 @@ public function index($locale): \Illuminate\Http\JsonResponse { $perPage = request()->query('per_page', 10); - $projects = Project::with('translations', 'category.translations')->paginate($perPage); + $projects = Project::with('category')->paginate($perPage); $formattedProjects = $projects->items(); $formattedProjects = array_map(function ($project) use ($locale) { - $translation = $project->translations->firstWhere('locale', $locale); - $categoryTranslation = $project->category->translations->firstWhere('locale', $locale); - return [ 'id' => $project->id, - 'title' => $translation ? $translation->title : null, - 'category' => $categoryTranslation ? $categoryTranslation->title : null, + 'title' => $project->getTranslation('title', $locale), + 'category' => $project->category?->getTranslation('title', $locale), 'images' => [ ['src' => $project->image1, 'position' => '1', 'zIndex' => 1], ['src' => $project->image2, 'position' => '2', 'zIndex' => 2], @@ -44,7 +41,7 @@ public function index($locale): \Illuminate\Http\JsonResponse 'categories' => ProjectCategory::all()->map(function ($category) use ($locale) { return [ 'id' => $category->id, - 'title' => $category->translations->firstWhere('locale', $locale)->title ?? null, + 'title' => $category->getTranslation('title', $locale), 'image' => $category->image, ]; }), @@ -56,27 +53,22 @@ public function store(StoreProjectRequest $request) $project = Project::create($request->only(['category_id', 'image1', 'image2'])); foreach (['fa', 'en'] as $locale) { - $project->translations()->create([ - 'locale' => $locale, - 'title' => $request->input("title_$locale"), - 'description' => $request->input("description_$locale"), - ]); + $project->setTranslation('title', $locale, $request->input("title_$locale")); + $project->setTranslation('description', $locale, $request->input("description_$locale")); } + $project->save(); - return response()->json($project->load('translations'), 201); + return response()->json($project, 201); } - public function show($id) + public function show($id, $locale) { - $project = Project::with('translations', 'category.translations')->findOrFail($id); - - $translation = $project->translations->firstWhere('locale', 'fa'); - $categoryTranslation = $project->category->translations->firstWhere('locale', 'fa'); + $project = Project::with('category')->findOrFail($id); $formattedProject = [ 'id' => $project->id, - 'title' => $translation ? $translation->title : null, - 'category' => $categoryTranslation ? $categoryTranslation->title : null, + 'title' => $project->getTranslation('title', $locale), + 'category' => $project->category?->getTranslation('title', 'fa'), 'images' => [ ['src' => $project->image1, 'position' => '1', 'zIndex' => 1], ['src' => $project->image2, 'position' => '2', 'zIndex' => 2], @@ -94,16 +86,16 @@ public function update(UpdateProjectRequest $request, $id) $project->update($request->only(['category_id', 'image1', 'image2'])); foreach (['fa', 'en'] as $locale) { - $project->translations()->updateOrCreate( - ['locale' => $locale], - [ - 'title' => $request->input("title_$locale"), - 'description' => $request->input("description_$locale"), - ] - ); + if ($request->has("title_$locale")) { + $project->setTranslation('title', $locale, $request->input("title_$locale")); + } + if ($request->has("description_$locale")) { + $project->setTranslation('description', $locale, $request->input("description_$locale")); + } } + $project->save(); - return response()->json($project->load('translations')); + return response()->json($project); } public function destroy($id) diff --git a/app/Http/Controllers/Api/Website/ServiceController.php b/app/Http/Controllers/Api/Website/ServiceController.php index 53e9116..eda031c 100644 --- a/app/Http/Controllers/Api/Website/ServiceController.php +++ b/app/Http/Controllers/Api/Website/ServiceController.php @@ -9,19 +9,17 @@ class ServiceController extends Controller { public function index($locale) { - $services = Service::with(['translations' => function ($query) use ($locale) { - $query->where('locale', $locale); - }])->get(); + $services = Service::all(); $response = $services->map(function ($service) use ($locale) { return [ 'id' => $service->id, 'imageSrc' => $service->imageSrc, - 'altText' => $service->translations->first()->altText ?? '', + 'altText' => '', 'created_at' => $service->created_at, 'updated_at' => $service->updated_at, - 'title' => optional($service->translations->first())->title, - 'description' => optional($service->translations->first())->description, + 'title' => $service->getTranslation('title', $locale), + 'description' => $service->getTranslation('description', $locale), ]; }); @@ -30,13 +28,13 @@ public function index($locale) public function showTranslation($id, $locale) { - $service = Service::with('translations')->findOrFail($id); + $service = Service::findOrFail($id); return response()->json([ 'id' => $service->id, 'imageSrc' => $service->imageSrc, - 'title' => $service->getTitle($locale), - 'description' => $service->getDescription($locale), + 'title' => $service->getTranslation('title', $locale), + 'description' => $service->getTranslation('description', $locale), ]); } } diff --git a/app/Models/Blog/Category.php b/app/Models/Blog/Category.php index 33d3622..86124cd 100644 --- a/app/Models/Blog/Category.php +++ b/app/Models/Blog/Category.php @@ -8,7 +8,7 @@ class Category extends Model { use HasFactory; - + protected $fillable = [ 'name', 'slug', diff --git a/app/Models/Website/AboutMe.php b/app/Models/Website/AboutMe.php index 4f4fac7..064da97 100644 --- a/app/Models/Website/AboutMe.php +++ b/app/Models/Website/AboutMe.php @@ -4,50 +4,16 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Spatie\Translatable\HasTranslations; class AboutMe extends Model { - use HasFactory; + use HasFactory, HasTranslations; protected $table = 'website_about_me'; protected $fillable = ['image']; - /** - * ارتباط با ترجمه‌ها - */ - public function translations() - { - return $this->hasMany(AboutMeTranslation::class, 'about_me_id'); - } - - /** - * گرفتن ترجمه بر اساس زبان فعلی - */ - public function getTranslation($locale) - { - if ($this->relationLoaded('translations')) { - return $this->translations->firstWhere('locale', $locale); - } - return $this->translations()->where('locale', $locale)->first(); - } - - /** - * گرفتن عنوان بر اساس زبان فعلی - */ - public function getTitle($locale) - { - $translation = $this->getTranslation($locale); - return $translation ? $translation->title : null; - } - - /** - * گرفتن توضیحات بر اساس زبان فعلی - */ - public function getDescription($locale) - { - $translation = $this->getTranslation($locale); - return $translation ? $translation->description : null; - } + protected array $translatable = ['title', 'description']; public function skills() { diff --git a/app/Models/Website/AboutMeTranslation.php b/app/Models/Website/AboutMeTranslation.php deleted file mode 100644 index 03fa923..0000000 --- a/app/Models/Website/AboutMeTranslation.php +++ /dev/null @@ -1,22 +0,0 @@ -belongsTo(AboutMe::class, 'about_me_id'); - } -} diff --git a/app/Models/Website/Project.php b/app/Models/Website/Project.php index 4474481..dd41339 100644 --- a/app/Models/Website/Project.php +++ b/app/Models/Website/Project.php @@ -4,18 +4,16 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Spatie\Translatable\HasTranslations; class Project extends Model { - use HasFactory; + use HasFactory, HasTranslations; - protected $table = 'website_projects'; // نام صحیح جدول + protected $table = 'website_projects'; protected $fillable = ['category_id', 'image1', 'image2']; - public function translations() - { - return $this->hasMany(ProjectTranslation::class, 'project_id'); - } + protected array $translatable = ['title', 'description']; public function category() { diff --git a/app/Models/Website/ProjectCategory.php b/app/Models/Website/ProjectCategory.php index 0ce72a8..f27d81a 100644 --- a/app/Models/Website/ProjectCategory.php +++ b/app/Models/Website/ProjectCategory.php @@ -4,16 +4,14 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Spatie\Translatable\HasTranslations; class ProjectCategory extends Model { - use HasFactory; + use HasFactory, HasTranslations; - protected $table = 'website_project_categories'; // نام صحیح جدول + protected $table = 'website_project_categories'; protected $fillable = ['image']; - public function translations() - { - return $this->hasMany(ProjectCategoryTranslation::class, 'category_id'); - } + protected array $translatable = ['title', 'description']; } diff --git a/app/Models/Website/ProjectCategoryTranslation.php b/app/Models/Website/ProjectCategoryTranslation.php deleted file mode 100644 index 750f2e0..0000000 --- a/app/Models/Website/ProjectCategoryTranslation.php +++ /dev/null @@ -1,14 +0,0 @@ -belongsTo(Project::class, 'project_id'); - } -} diff --git a/app/Models/Website/Service.php b/app/Models/Website/Service.php index 9f8193b..de023e1 100644 --- a/app/Models/Website/Service.php +++ b/app/Models/Website/Service.php @@ -4,48 +4,14 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Spatie\Translatable\HasTranslations; class Service extends Model { - use HasFactory; + use HasFactory, HasTranslations; - protected $table = 'website_services'; // نام جدول - protected $fillable = ['imageSrc']; // فیلدهایی که قابل پر کردن هستند + protected $table = 'website_services'; + protected $fillable = ['imageSrc']; - /** - * ارتباط با جدول ترجمه‌ها - */ - public function translations() - { - return $this->hasMany(ServiceTranslation::class, 'service_id'); - } - - /** - * گرفتن ترجمه بر اساس زبان فعلی - */ - public function getTranslation($locale) - { - if ($this->relationLoaded('translations')) { - return $this->translations->firstWhere('locale', $locale); - } - return $this->translations()->where('locale', $locale)->first(); - } - - /** - * گرفتن عنوان بر اساس زبان فعلی - */ - public function getTitle($locale) - { - $translation = $this->getTranslation($locale); - return $translation ? $translation->title : null; - } - - /** - * گرفتن توضیحات بر اساس زبان فعلی - */ - public function getDescription($locale) - { - $translation = $this->getTranslation($locale); - return $translation ? $translation->description : null; - } + protected array $translatable = ['title', 'description']; } diff --git a/app/Models/Website/ServiceTranslation.php b/app/Models/Website/ServiceTranslation.php deleted file mode 100644 index c10d4ea..0000000 --- a/app/Models/Website/ServiceTranslation.php +++ /dev/null @@ -1,22 +0,0 @@ -belongsTo(Service::class, 'service_id'); - } -} diff --git a/app/Models/Website/Skill.php b/app/Models/Website/Skill.php index 2caa13d..b4e24f4 100644 --- a/app/Models/Website/Skill.php +++ b/app/Models/Website/Skill.php @@ -4,56 +4,19 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Spatie\Translatable\HasTranslations; class Skill extends Model { - use HasFactory; + use HasFactory, HasTranslations; - protected $table = 'website_skills'; // نام جدول - protected $fillable = ['image', 'link', 'about_me_id', 'percentage']; // فیلدهایی که قابل پر کردن هستند + protected $table = 'website_skills'; + protected $fillable = ['image', 'link', 'about_me_id', 'percentage']; + + protected array $translatable = ['title', 'description']; - /** - * ارتباط با جدول AboutMe - */ public function aboutMe() { return $this->belongsTo(AboutMe::class, 'about_me_id'); } - - /** - * ارتباط با جدول ترجمه‌ها - */ - public function translations() - { - return $this->hasMany(SkillTranslation::class, 'skill_id'); - } - - /** - * گرفتن ترجمه بر اساس زبان فعلی - */ - public function getTranslation($locale) - { - if ($this->relationLoaded('translations')) { - return $this->translations->firstWhere('locale', $locale); - } - return $this->translations()->where('locale', $locale)->first(); - } - - /** - * گرفتن عنوان بر اساس زبان فعلی - */ - public function getTitle($locale) - { - $translation = $this->getTranslation($locale); - return $translation ? $translation->title : null; - } - - /** - * گرفتن توضیحات بر اساس زبان فعلی - */ - public function getDescription($locale) - { - $translation = $this->getTranslation($locale); - return $translation ? $translation->description : null; - } } diff --git a/app/Models/Website/SkillTranslation.php b/app/Models/Website/SkillTranslation.php deleted file mode 100644 index d656929..0000000 --- a/app/Models/Website/SkillTranslation.php +++ /dev/null @@ -1,22 +0,0 @@ -belongsTo(Skill::class, 'skill_id'); - } -} diff --git a/composer.json b/composer.json index 2a656a1..6e888b0 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "php": "^8.2", "laravel/framework": "^11.9", "laravel/sanctum": "^4.0", - "laravel/tinker": "^2.9" + "laravel/tinker": "^2.9", + "spatie/laravel-translatable": "^6.14" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index afed53a..1e520ee 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eba04bbb24b3180d2a6614993a886c4b", + "content-hash": "440d7629e9c875514932936ef5c0c454", "packages": [ { "name": "brick/math", @@ -3175,6 +3175,150 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "spatie/laravel-package-tools", + "version": "1.93.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "d5552849801f2642aea710557463234b59ef65eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d5552849801f2642aea710557463234b59ef65eb", + "reference": "d5552849801f2642aea710557463234b59ef65eb", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0", + "pestphp/pest": "^2.1|^3.1|^4.0", + "phpunit/php-code-coverage": "^10.0|^11.0|^12.0", + "phpunit/phpunit": "^10.5|^11.5|^12.5", + "spatie/pest-plugin-test-time": "^2.2|^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-05-19T14:06:37+00:00" + }, + { + "name": "spatie/laravel-translatable", + "version": "6.14.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-translatable.git", + "reference": "d120a925cf413b2427f886264bb6eb102ac23d42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-translatable/zipball/d120a925cf413b2427f886264bb6eb102ac23d42", + "reference": "d120a925cf413b2427f886264bb6eb102ac23d42", + "shasum": "" + }, + "require": { + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "php": "^8.3", + "spatie/laravel-package-tools": "^1.93.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.90", + "mockery/mockery": "^1.6.12", + "orchestra/testbench": "^9.0|^10.0|^11.0", + "pestphp/pest": "^4.0.0" + }, + "type": "library", + "extra": { + "aliases": { + "Translatable": "Spatie\\Translatable\\Facades\\Translatable" + }, + "laravel": { + "providers": [ + "Spatie\\Translatable\\TranslatableServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Translatable\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A trait to make an Eloquent model hold translations", + "homepage": "https://github.com/spatie/laravel-translatable", + "keywords": [ + "eloquent", + "i8n", + "laravel-translatable", + "model", + "multilingual", + "spatie", + "translate" + ], + "support": { + "issues": "https://github.com/spatie/laravel-translatable/issues", + "source": "https://github.com/spatie/laravel-translatable/tree/6.14.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-04-23T08:26:29+00:00" + }, { "name": "symfony/clock", "version": "v7.1.1", diff --git a/database/migrations/2026_06_23_145429_migrate_to_spatie_translatable.php b/database/migrations/2026_06_23_145429_migrate_to_spatie_translatable.php new file mode 100644 index 0000000..c654411 --- /dev/null +++ b/database/migrations/2026_06_23_145429_migrate_to_spatie_translatable.php @@ -0,0 +1,248 @@ +json('translations')->nullable()->after('image'); + }); + $rows = DB::table('website_about_me_translations')->get(); + foreach ($rows->groupBy('about_me_id') as $aboutMeId => $translations) { + $data = []; + foreach ($translations as $t) { + $data['title'][$t->locale] = $t->title; + $data['description'][$t->locale] = $t->description; + } + DB::table('website_about_me')->where('id', $aboutMeId)->update(['translations' => json_encode($data)]); + } + Schema::dropIfExists('website_about_me_translations'); + + // website_skills + Schema::table('website_skills', function (Blueprint $table) { + $table->json('translations')->nullable()->after('percentage'); + }); + $rows = DB::table('website_skill_translations')->get(); + foreach ($rows->groupBy('skill_id') as $skillId => $translations) { + $data = []; + foreach ($translations as $t) { + $data['title'][$t->locale] = $t->title; + $data['description'][$t->locale] = $t->description; + } + DB::table('website_skills')->where('id', $skillId)->update(['translations' => json_encode($data)]); + } + Schema::dropIfExists('website_skill_translations'); + + // website_services + Schema::table('website_services', function (Blueprint $table) { + $table->json('translations')->nullable()->after('imageSrc'); + }); + $rows = DB::table('website_service_translations')->get(); + foreach ($rows->groupBy('service_id') as $serviceId => $translations) { + $data = []; + foreach ($translations as $t) { + $data['title'][$t->locale] = $t->title; + $data['description'][$t->locale] = $t->description; + } + DB::table('website_services')->where('id', $serviceId)->update(['translations' => json_encode($data)]); + } + Schema::dropIfExists('website_service_translations'); + + // website_project_categories + Schema::table('website_project_categories', function (Blueprint $table) { + $table->json('translations')->nullable()->after('image'); + }); + $rows = DB::table('website_project_category_translations')->get(); + foreach ($rows->groupBy('category_id') as $categoryId => $translations) { + $data = []; + foreach ($translations as $t) { + $data['title'][$t->locale] = $t->title; + $data['description'][$t->locale] = $t->description; + } + DB::table('website_project_categories')->where('id', $categoryId)->update(['translations' => json_encode($data)]); + } + Schema::dropIfExists('website_project_category_translations'); + + // website_projects + Schema::table('website_projects', function (Blueprint $table) { + $table->json('translations')->nullable()->after('image2'); + }); + $rows = DB::table('website_project_translations')->get(); + foreach ($rows->groupBy('project_id') as $projectId => $translations) { + $data = []; + foreach ($translations as $t) { + $data['title'][$t->locale] = $t->title; + $data['description'][$t->locale] = $t->description; + } + DB::table('website_projects')->where('id', $projectId)->update(['translations' => json_encode($data)]); + } + Schema::dropIfExists('website_project_translations'); + + // === BLOG TABLES === + Schema::table('posts', function (Blueprint $table) { + $table->json('translations')->nullable()->after('featured_image'); + }); + Schema::table('categories', function (Blueprint $table) { + $table->json('translations')->nullable()->after('image'); + }); + Schema::table('tags', function (Blueprint $table) { + $table->json('translations')->nullable()->after('slug'); + }); + } + + public function down(): void + { + // Restore website_about_me_translations + Schema::create('website_about_me_translations', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('about_me_id'); + $table->string('locale')->index(); + $table->string('title'); + $table->text('description'); + $table->timestamps(); + $table->foreign('about_me_id')->references('id')->on('website_about_me')->onDelete('cascade'); + $table->unique(['about_me_id', 'locale']); + }); + DB::table('website_about_me')->whereNotNull('translations')->each(function ($row) { + $translations = json_decode($row->translations, true); + if (!$translations) return; + foreach (($translations['title'] ?? []) as $locale => $title) { + DB::table('website_about_me_translations')->insert([ + 'about_me_id' => $row->id, + 'locale' => $locale, + 'title' => $title, + 'description' => $translations['description'][$locale] ?? '', + ]); + } + }); + Schema::table('website_about_me', function (Blueprint $table) { + $table->dropColumn('translations'); + }); + + // Restore website_skill_translations + Schema::create('website_skill_translations', function (Blueprint $table) { + $table->id(); + $table->foreignId('skill_id')->constrained('website_skills')->onDelete('cascade'); + $table->string('locale')->index(); + $table->string('title'); + $table->text('description'); + $table->unique(['skill_id', 'locale']); + $table->timestamps(); + }); + DB::table('website_skills')->whereNotNull('translations')->each(function ($row) { + $translations = json_decode($row->translations, true); + if (!$translations) return; + foreach (($translations['title'] ?? []) as $locale => $title) { + DB::table('website_skill_translations')->insert([ + 'skill_id' => $row->id, + 'locale' => $locale, + 'title' => $title, + 'description' => $translations['description'][$locale] ?? '', + ]); + } + }); + Schema::table('website_skills', function (Blueprint $table) { + $table->dropColumn('translations'); + }); + + // Restore website_service_translations + Schema::create('website_service_translations', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('service_id'); + $table->string('locale')->index(); + $table->string('title'); + $table->text('description'); + $table->timestamps(); + $table->foreign('service_id')->references('id')->on('website_services')->onDelete('cascade'); + $table->unique(['service_id', 'locale']); + }); + DB::table('website_services')->whereNotNull('translations')->each(function ($row) { + $translations = json_decode($row->translations, true); + if (!$translations) return; + foreach (($translations['title'] ?? []) as $locale => $title) { + DB::table('website_service_translations')->insert([ + 'service_id' => $row->id, + 'locale' => $locale, + 'title' => $title, + 'description' => $translations['description'][$locale] ?? '', + ]); + } + }); + Schema::table('website_services', function (Blueprint $table) { + $table->dropColumn('translations'); + }); + + // Restore website_project_category_translations + Schema::create('website_project_category_translations', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('category_id'); + $table->string('locale')->index(); + $table->string('title'); + $table->text('description'); + $table->timestamps(); + $table->foreign('category_id')->references('id')->on('website_project_categories')->onDelete('cascade'); + $table->unique(['category_id', 'locale']); + }); + DB::table('website_project_categories')->whereNotNull('translations')->each(function ($row) { + $translations = json_decode($row->translations, true); + if (!$translations) return; + foreach (($translations['title'] ?? []) as $locale => $title) { + DB::table('website_project_category_translations')->insert([ + 'category_id' => $row->id, + 'locale' => $locale, + 'title' => $title, + 'description' => $translations['description'][$locale] ?? '', + ]); + } + }); + Schema::table('website_project_categories', function (Blueprint $table) { + $table->dropColumn('translations'); + }); + + // Restore website_project_translations + Schema::create('website_project_translations', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('project_id'); + $table->string('locale')->index(); + $table->string('title'); + $table->text('description'); + $table->timestamps(); + $table->foreign('project_id')->references('id')->on('website_projects')->onDelete('cascade'); + $table->unique(['project_id', 'locale']); + }); + DB::table('website_projects')->whereNotNull('translations')->each(function ($row) { + $translations = json_decode($row->translations, true); + if (!$translations) return; + foreach (($translations['title'] ?? []) as $locale => $title) { + DB::table('website_project_translations')->insert([ + 'project_id' => $row->id, + 'locale' => $locale, + 'title' => $title, + 'description' => $translations['description'][$locale] ?? '', + ]); + } + }); + Schema::table('website_projects', function (Blueprint $table) { + $table->dropColumn('translations'); + }); + + // Blog tables + Schema::table('posts', function (Blueprint $table) { + $table->dropColumn('translations'); + }); + Schema::table('categories', function (Blueprint $table) { + $table->dropColumn('translations'); + }); + Schema::table('tags', function (Blueprint $table) { + $table->dropColumn('translations'); + }); + } +};