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');
+ });
+ }
+};