Версия 0.1.0 #1
@@ -3,15 +3,18 @@
 | 
				
			|||||||
namespace App\Dto\Service\Admin\Project;
 | 
					namespace App\Dto\Service\Admin\Project;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Dto\Service\Dto;
 | 
					use App\Dto\Service\Dto;
 | 
				
			||||||
 | 
					use App\Enums\Lang;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final readonly class Language extends Dto
 | 
					final readonly class Language extends Dto
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private string $title,
 | 
					        private string  $title,
 | 
				
			||||||
        private string $code,
 | 
					        private string  $code,
 | 
				
			||||||
        private int    $sort,
 | 
					        private int     $sort,
 | 
				
			||||||
        private bool   $isDefault,
 | 
					        private bool    $isDefault,
 | 
				
			||||||
        private ?int   $id,
 | 
					        private ?string $isoCode = null,
 | 
				
			||||||
 | 
					        private ?Lang   $systemLang = null,
 | 
				
			||||||
 | 
					        private ?int    $id = null,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getTitle(): string
 | 
					    public function getTitle(): string
 | 
				
			||||||
@@ -38,4 +41,14 @@ final readonly class Language extends Dto
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->id;
 | 
					        return $this->id;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getIsoCode(): ?string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->isoCode;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getSystemLang(): ?Lang
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->systemLang;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto\Service\Admin\Project\Translation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Service\Dto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class Translation extends Dto
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private string $code,
 | 
				
			||||||
 | 
					        private ?string $text
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getCode(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->code;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getText(): ?string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->text;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto\Service\Admin\Project\Translation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Exceptions\Dto\Admin\Project\Transaction\TranslationsException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class Translations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private array $translations = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function addTranslation(string $code, ?string $text): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!in_array($code, self::getTranslationCodes())) {
 | 
				
			||||||
 | 
					            throw new TranslationsException('Translation code "' . $code . '" not available');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        $this->translations[] = new Translation($code, $text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getTranslations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->translations;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getTranslationCodes(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'site.Menu',
 | 
				
			||||||
 | 
					            'site.Powered by service',
 | 
				
			||||||
 | 
					            'site.About project',
 | 
				
			||||||
 | 
					            'site.Choose language',
 | 
				
			||||||
 | 
					            'site.Page without translation',
 | 
				
			||||||
 | 
					            'site.Project',
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto\Service\Admin\Project\Translation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Service\Dto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class Update extends Dto
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private Translations $translations,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getTranslations(): Translations
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->translations;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Cache;
 | 
				
			|||||||
enum CacheTag: string
 | 
					enum CacheTag: string
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    case Project = 'project';
 | 
					    case Project = 'project';
 | 
				
			||||||
 | 
					    case ProjectTranslation = 'project_translation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getCache(): TaggedCache
 | 
					    public function getCache(): TaggedCache
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ enum Permission: string
 | 
				
			|||||||
    case Project = 'project';
 | 
					    case Project = 'project';
 | 
				
			||||||
    case ProjectContent = 'project-content';
 | 
					    case ProjectContent = 'project-content';
 | 
				
			||||||
    case ProjectLink = 'project-link';
 | 
					    case ProjectLink = 'project-link';
 | 
				
			||||||
 | 
					    case ProjectTranslation = 'project-translation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getPermissions(): array
 | 
					    public function getPermissions(): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -22,6 +23,10 @@ enum Permission: string
 | 
				
			|||||||
                'create' => __('permissions.Allowed to create'),
 | 
					                'create' => __('permissions.Allowed to create'),
 | 
				
			||||||
                'update' => __('permissions.Allowed to edit'),
 | 
					                'update' => __('permissions.Allowed to edit'),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
 | 
					            self::ProjectTranslation => [
 | 
				
			||||||
 | 
					                'view'   => __('permissions.Allowed to watch'),
 | 
				
			||||||
 | 
					                'update' => __('permissions.Allowed to edit'),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
            default => $this->getBasePermissions()
 | 
					            default => $this->getBasePermissions()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Exceptions\Dto\Admin\Project\Transaction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class TranslationsException extends \Exception
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Controllers\Admin\Projects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Http\Controllers\Controller;
 | 
				
			||||||
 | 
					use App\Http\Requests\Admin\Projects\Translations\UpdateRequest;
 | 
				
			||||||
 | 
					use App\Services\Admin\Project\TranslationService;
 | 
				
			||||||
 | 
					use Illuminate\Http\RedirectResponse;
 | 
				
			||||||
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class TranslationsController extends Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly TranslationService $translationService,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function languages(int $projectId, Request $request): View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $user = $request->user();
 | 
				
			||||||
 | 
					        $result = $this->translationService->languages($projectId, $user);
 | 
				
			||||||
 | 
					        if ($result->isError()) {
 | 
				
			||||||
 | 
					            $this->errors($result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return view('admin/projects/translations/languages', $result->getData());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function edit(int $projectId, int $languageId, Request $request): View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $user = $request->user();
 | 
				
			||||||
 | 
					        $result = $this->translationService->edit($projectId, $languageId, $user);
 | 
				
			||||||
 | 
					        if ($result->isError()) {
 | 
				
			||||||
 | 
					            $this->errors($result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return view('admin/projects/translations/edit', $result->getData());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function update(int $projectId, int $languageId, UpdateRequest $request): RedirectResponse
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $user = $request->user();
 | 
				
			||||||
 | 
					        $data = $request->getDto();
 | 
				
			||||||
 | 
					        $result = $this->translationService->update($projectId, $languageId, $data, $user);
 | 
				
			||||||
 | 
					        if ($result->isError()) {
 | 
				
			||||||
 | 
					            return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return redirect()->route('admin.projects.translations.edit', [
 | 
				
			||||||
 | 
					            'project' => $projectId,
 | 
				
			||||||
 | 
					            'language' => $languageId,
 | 
				
			||||||
 | 
					        ])->withSuccess($result->getMessage());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -12,7 +12,7 @@ abstract class Controller extends BaseController
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return \view('site.page-without-translation', [
 | 
					        return \view('site.page-without-translation', [
 | 
				
			||||||
            'project' => $result->getProject(),
 | 
					            'project' => $result->getProject(),
 | 
				
			||||||
            'language' => $result->getLanguage(),
 | 
					            'websiteTranslations' => $result->getWebsiteTranslations(),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Http\Controllers\Site;
 | 
					namespace App\Http\Controllers\Site;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Enums\Site\ProjectSection;
 | 
					 | 
				
			||||||
use App\Services\Site\ProjectService;
 | 
					use App\Services\Site\ProjectService;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use Illuminate\View\View;
 | 
					use Illuminate\View\View;
 | 
				
			||||||
@@ -13,15 +12,13 @@ final class ProjectsController extends Controller
 | 
				
			|||||||
        private readonly ProjectService $projectService,
 | 
					        private readonly ProjectService $projectService,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function index(Request $request, ?string $language = null): View
 | 
					    public function index(Request $request): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $user = $request->user();
 | 
					        $user    = $request->user();
 | 
				
			||||||
 | 
					        $project = $request->get('project');
 | 
				
			||||||
        if (\is_null($request->project)) {
 | 
					        $websiteTranslations = $request->get('websiteTranslations');
 | 
				
			||||||
            if (!\is_null($language)) {
 | 
					 | 
				
			||||||
                abort(404);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (\is_null($project)) {
 | 
				
			||||||
            $with = ['storage'];
 | 
					            $with = ['storage'];
 | 
				
			||||||
            $result = $this->projectService->getProjects($user, $with);
 | 
					            $result = $this->projectService->getProjects($user, $with);
 | 
				
			||||||
            if ($result->isError()) {
 | 
					            if ($result->isError()) {
 | 
				
			||||||
@@ -31,7 +28,7 @@ final class ProjectsController extends Controller
 | 
				
			|||||||
            return \view('site.projects.index', $result->getData());
 | 
					            return \view('site.projects.index', $result->getData());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $result = $this->projectService->getAboutByProject($request->project, $language, $request->user());
 | 
					        $result = $this->projectService->getAboutByProject($project, $websiteTranslations, $request->user());
 | 
				
			||||||
        if ($result->isError()) {
 | 
					        if ($result->isError()) {
 | 
				
			||||||
            $this->errors($result);
 | 
					            $this->errors($result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,23 +3,17 @@
 | 
				
			|||||||
namespace App\Http\Middleware;
 | 
					namespace App\Http\Middleware;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Enums\CacheTag;
 | 
					use App\Enums\CacheTag;
 | 
				
			||||||
use App\Repositories\ProjectRepository;
 | 
					 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use Illuminate\Support\Str;
 | 
					 | 
				
			||||||
use Symfony\Component\HttpFoundation\Response;
 | 
					use Symfony\Component\HttpFoundation\Response;
 | 
				
			||||||
use Closure;
 | 
					use Closure;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Project
 | 
					class ProjectAndLanguage extends ProjectLanguage
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					 | 
				
			||||||
        private readonly ProjectRepository $projectRepository,
 | 
					 | 
				
			||||||
    ) { }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function handle(Request $request, Closure $next): Response
 | 
					    public function handle(Request $request, Closure $next): Response
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $projectCode = $request->route()?->parameter('project');
 | 
					        $projectCode = $request->route()?->parameter('project');
 | 
				
			||||||
        if ($projectCode === null) {
 | 
					        if ($projectCode === null) {
 | 
				
			||||||
            abort(404);
 | 
					            abort(Response::HTTP_NOT_FOUND);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $seconds = 3600;
 | 
					        $seconds = 3600;
 | 
				
			||||||
@@ -27,23 +21,27 @@ class Project
 | 
				
			|||||||
            return $this->projectRepository->getProjectByCode($projectCode) ?? false;
 | 
					            return $this->projectRepository->getProjectByCode($projectCode) ?? false;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        if ($project === false) {
 | 
					        if ($project === false) {
 | 
				
			||||||
            abort(404);
 | 
					            abort(Response::HTTP_NOT_FOUND);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            $project->http_host !== null
 | 
				
			||||||
 | 
					            && $project->http_host !== $request->getSchemeAndHttpHost()
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            return redirect($project->http_host, 302);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $languageCode = $request->route()?->parameter('language');
 | 
				
			||||||
 | 
					        $websiteTranslations = $this->getWebsiteTranslations($project, $languageCode);
 | 
				
			||||||
 | 
					        if (\is_null($websiteTranslations)) {
 | 
				
			||||||
 | 
					            abort(Response::HTTP_NOT_FOUND);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unset($request->route()->parameters['project']);
 | 
					        unset($request->route()->parameters['project']);
 | 
				
			||||||
 | 
					        unset($request->route()->parameters['language']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $routeName = $request->route()->getName();
 | 
					        $request->attributes->set('project', $project);
 | 
				
			||||||
        if (
 | 
					        $request->attributes->set('websiteTranslations', $websiteTranslations);
 | 
				
			||||||
            $routeName !== null
 | 
					 | 
				
			||||||
            && $project->http_host !== null
 | 
					 | 
				
			||||||
            && $project->http_host !== $request->getSchemeAndHttpHost()
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            $routeName = Str::of($routeName)->replaceStart('project.', '')->toString();
 | 
					 | 
				
			||||||
            $route = \route($routeName, $request->route()->parameters(), false);
 | 
					 | 
				
			||||||
            return redirect($project->http_host . $route, 302);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $request->merge(['project' => $project]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $next($request);
 | 
					        return $next($request);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -3,17 +3,12 @@
 | 
				
			|||||||
namespace App\Http\Middleware;
 | 
					namespace App\Http\Middleware;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Enums\CacheTag;
 | 
					use App\Enums\CacheTag;
 | 
				
			||||||
use App\Repositories\ProjectRepository;
 | 
					 | 
				
			||||||
use Closure;
 | 
					use Closure;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use Symfony\Component\HttpFoundation\Response;
 | 
					use Symfony\Component\HttpFoundation\Response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class ProjectDomain
 | 
					final class ProjectDomainAndLanguage extends ProjectLanguage
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					 | 
				
			||||||
        private readonly ProjectRepository $projectRepository,
 | 
					 | 
				
			||||||
    ) { }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function handle(Request $request, Closure $next): Response
 | 
					    public function handle(Request $request, Closure $next): Response
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $httpHost = $request->getSchemeAndHttpHost();
 | 
					        $httpHost = $request->getSchemeAndHttpHost();
 | 
				
			||||||
@@ -25,7 +20,19 @@ final class ProjectDomain
 | 
				
			|||||||
        if ($project === false) {
 | 
					        if ($project === false) {
 | 
				
			||||||
            $project = null;
 | 
					            $project = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        $request->merge(['project' => $project]);
 | 
					
 | 
				
			||||||
 | 
					        $websiteTranslations = null;
 | 
				
			||||||
 | 
					        if ($project !== null) {
 | 
				
			||||||
 | 
					            $languageCode = $request->route()?->parameter('language');
 | 
				
			||||||
 | 
					            $websiteTranslations = $this->getWebsiteTranslations($project, $languageCode);
 | 
				
			||||||
 | 
					            if (\is_null($websiteTranslations)) {
 | 
				
			||||||
 | 
					                abort(Response::HTTP_NOT_FOUND);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        unset($request->route()->parameters['language']);
 | 
				
			||||||
 | 
					        $request->attributes->set('project', $project);
 | 
				
			||||||
 | 
					        $request->attributes->set('websiteTranslations', $websiteTranslations);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $next($request);
 | 
					        return $next($request);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
							
								
								
									
										43
									
								
								app/application/app/Http/Middleware/ProjectLanguage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								app/application/app/Http/Middleware/ProjectLanguage.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Middleware;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\CacheTag;
 | 
				
			||||||
 | 
					use App\Models\Project;
 | 
				
			||||||
 | 
					use App\Repositories\ProjectLanguageRepository;
 | 
				
			||||||
 | 
					use App\Repositories\ProjectRepository;
 | 
				
			||||||
 | 
					use App\Repositories\ProjectTranslationRepository;
 | 
				
			||||||
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\App;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class ProjectLanguage
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        protected readonly ProjectRepository $projectRepository,
 | 
				
			||||||
 | 
					        private   readonly ProjectLanguageRepository $projectLanguageRepository,
 | 
				
			||||||
 | 
					        private   readonly ProjectTranslationRepository $projectTranslationRepository
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getWebsiteTranslations(Project $project, ?string $languageCode): ?WebsiteTranslations
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $seconds = 3600 * 3;
 | 
				
			||||||
 | 
					        $language = CacheTag::Project->getCache()->remember(self::class . $project->id . '-' . $languageCode, $seconds, function () use ($project, $languageCode) {
 | 
				
			||||||
 | 
					            return $this->projectLanguageRepository->getProjectLanguageByCodeOrDefault($project, $languageCode) ?? false;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        if ($language === false) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if ($language !== null) {
 | 
				
			||||||
 | 
					            if ($language->system_lang) {
 | 
				
			||||||
 | 
					                App::setLocale($language->system_lang->getLocale());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $seconds = 3600 * 24;
 | 
				
			||||||
 | 
					        $translations = CacheTag::ProjectTranslation->getCache()->remember(self::class . '-translations-' . $project->id . '-' . $language->id, $seconds, function () use ($project, $language) {
 | 
				
			||||||
 | 
					            return $this->projectTranslationRepository->getProjectTranslations($project->id, $language->id)->all()->pluck('text', 'code')->toArray();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new WebsiteTranslations($language, $translations);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,9 +7,11 @@ use App\Dto\Service\Admin\Project\Language;
 | 
				
			|||||||
use App\Dto\Service\Admin\Project\Languages;
 | 
					use App\Dto\Service\Admin\Project\Languages;
 | 
				
			||||||
use App\Dto\Service\Admin\Project\StoreUpdate;
 | 
					use App\Dto\Service\Admin\Project\StoreUpdate;
 | 
				
			||||||
use App\Dto\Service\Storage\Storages;
 | 
					use App\Dto\Service\Storage\Storages;
 | 
				
			||||||
 | 
					use App\Enums\Lang;
 | 
				
			||||||
use App\Enums\StorageType;
 | 
					use App\Enums\StorageType;
 | 
				
			||||||
use App\Rules\HttpHost;
 | 
					use App\Rules\HttpHost;
 | 
				
			||||||
use Illuminate\Foundation\Http\FormRequest;
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					use Illuminate\Validation\Rules\Enum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
					class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -33,6 +35,8 @@ class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
            'languages.items.*.title' => ['required', 'string', 'max:255'],
 | 
					            'languages.items.*.title' => ['required', 'string', 'max:255'],
 | 
				
			||||||
            'languages.items.*.code' => ['required', 'string', 'min:2', 'max:30', 'regex:/^[a-z_]+$/'],
 | 
					            'languages.items.*.code' => ['required', 'string', 'min:2', 'max:30', 'regex:/^[a-z_]+$/'],
 | 
				
			||||||
            'languages.items.*.sort' => ['required', 'numeric', 'min:-1000', 'max:1000'],
 | 
					            'languages.items.*.sort' => ['required', 'numeric', 'min:-1000', 'max:1000'],
 | 
				
			||||||
 | 
					            'languages.items.*.system_lang' => ['nullable', new Enum(Lang::class)],
 | 
				
			||||||
 | 
					            'languages.items.*.iso_code' => ['nullable', 'string', 'max:30', 'regex:/^[a-zA-Z-]+$/'],
 | 
				
			||||||
            'languages.default' => ['required', 'numeric', function (string $attribute, mixed $value, \Closure $fail) {
 | 
					            'languages.default' => ['required', 'numeric', function (string $attribute, mixed $value, \Closure $fail) {
 | 
				
			||||||
                $languages = $this->input('languages.items', []);
 | 
					                $languages = $this->input('languages.items', []);
 | 
				
			||||||
                if (!isset($languages[$value])) {
 | 
					                if (!isset($languages[$value])) {
 | 
				
			||||||
@@ -81,11 +85,17 @@ class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
            if ($languageId !== null) {
 | 
					            if ($languageId !== null) {
 | 
				
			||||||
                $languageId = (int) $languageId;
 | 
					                $languageId = (int) $languageId;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            $systemLang = null;
 | 
				
			||||||
 | 
					            if ($lang['system_lang'] !== null) {
 | 
				
			||||||
 | 
					                $systemLang = Lang::tryFrom((int) $lang['system_lang']);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            $language = new Language(
 | 
					            $language = new Language(
 | 
				
			||||||
                title:     $lang['title'],
 | 
					                title:     $lang['title'],
 | 
				
			||||||
                code:      $lang['code'],
 | 
					                code:      $lang['code'],
 | 
				
			||||||
                sort:      (int) $lang['sort'],
 | 
					                sort:      (int) $lang['sort'],
 | 
				
			||||||
                isDefault: ($default === $index),
 | 
					                isDefault: ($default === $index),
 | 
				
			||||||
 | 
					                isoCode: $lang['iso_code'] ?? null,
 | 
				
			||||||
 | 
					                systemLang: $systemLang,
 | 
				
			||||||
                id: $languageId,
 | 
					                id: $languageId,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            $languages->addLanguage($language);
 | 
					            $languages->addLanguage($language);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Requests\Admin\Projects\Translations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Contracts\FormRequestDto;
 | 
				
			||||||
 | 
					use App\Dto\Service\Admin\Project\Translation\Translation;
 | 
				
			||||||
 | 
					use App\Dto\Service\Admin\Project\Translation\Translations;
 | 
				
			||||||
 | 
					use App\Dto\Service\Admin\Project\Translation\Update;
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					use Illuminate\Validation\Rules\In;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class UpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the validation rules that apply to the request.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function rules(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'translations'   => ['nullable', 'array'],
 | 
				
			||||||
 | 
					            'translations.*.code' => ['required', 'string', new In(Translations::getTranslationCodes())],
 | 
				
			||||||
 | 
					            'translations.*.text' => ['nullable', 'string', 'max:1000'],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getDto(): Update
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $translations = new Translations();
 | 
				
			||||||
 | 
					        foreach ($this->input('translations', []) as $translation) {
 | 
				
			||||||
 | 
					            $translations->addTranslation(
 | 
				
			||||||
 | 
					                code: $translation['code'],
 | 
				
			||||||
 | 
					                text: $translation['text'] ?? null,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new Update($translations);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,7 +2,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Models;
 | 
					namespace App\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\Lang;
 | 
				
			||||||
use App\Models\Scopes\SortScope;
 | 
					use App\Models\Scopes\SortScope;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Casts\Attribute;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
					use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Model;
 | 
					use Illuminate\Database\Eloquent\Model;
 | 
				
			||||||
use Illuminate\Database\Eloquent\SoftDeletes;
 | 
					use Illuminate\Database\Eloquent\SoftDeletes;
 | 
				
			||||||
@@ -33,6 +35,8 @@ final class ProjectLanguage extends Model
 | 
				
			|||||||
        'code',
 | 
					        'code',
 | 
				
			||||||
        'is_default',
 | 
					        'is_default',
 | 
				
			||||||
        'sort',
 | 
					        'sort',
 | 
				
			||||||
 | 
					        'system_lang',
 | 
				
			||||||
 | 
					        'iso_code',
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -43,8 +47,27 @@ final class ProjectLanguage extends Model
 | 
				
			|||||||
    protected function casts(): array
 | 
					    protected function casts(): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            'is_default' => 'boolean',
 | 
					            'is_default'  => 'boolean',
 | 
				
			||||||
            'sort'       => 'integer',
 | 
					            'sort'        => 'integer',
 | 
				
			||||||
 | 
					            'system_lang' => Lang::class,
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function attributeLang(): Attribute
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Attribute::make(
 | 
				
			||||||
 | 
					            get: function () {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ($this->iso_code) {
 | 
				
			||||||
 | 
					                    return $this->iso_code;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ($this->system_lang) {
 | 
				
			||||||
 | 
					                    return $this->system_lang->getLocale();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return $this->code;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )->shouldCache();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								app/application/app/Models/ProjectTranslation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/application/app/Models/ProjectTranslation.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Model;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\SoftDeletes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class ProjectTranslation extends Model
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use HasFactory, SoftDeletes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected $table = 'project_translations';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The attributes that are mass assignable.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $fillable = [
 | 
				
			||||||
 | 
					        'project_id',
 | 
				
			||||||
 | 
					        'language_id',
 | 
				
			||||||
 | 
					        'text',
 | 
				
			||||||
 | 
					        'code',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								app/application/app/Policies/ProjectTranslationPolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/application/app/Policies/ProjectTranslationPolicy.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Policies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\ProjectTranslation;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class ProjectTranslationPolicy extends Policy
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function viewAny(User $user): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $user->hasPermission('project.view');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function view(User $user, ProjectTranslation $translation): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $user->hasPermission('project-translation.view');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function update(User $user): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $user->hasPermission('project-translation.update');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Repositories;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\ProjectTranslation;
 | 
				
			||||||
 | 
					use App\Services\Search\CreateSearchInstanceCommand;
 | 
				
			||||||
 | 
					use App\Services\Search\Search;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class ProjectTranslationRepository
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private CreateSearchInstanceCommand $createSearchInstanceCommand,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					    public function getProjectTranslations(int $projectId, int $languageId): Search
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $query = ProjectTranslation::query()->where('project_id', $projectId)->where('language_id', $languageId);
 | 
				
			||||||
 | 
					        return $this->createSearchInstanceCommand->execute($query);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getProjectTranslationsWithTrashed(int $projectId, int $languageId): Search
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $query = ProjectTranslation::query()->withTrashed()->where('project_id', $projectId)->where('language_id', $languageId);
 | 
				
			||||||
 | 
					        return $this->createSearchInstanceCommand->execute($query);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,14 +3,14 @@
 | 
				
			|||||||
namespace app\ServiceResults\Site;
 | 
					namespace app\ServiceResults\Site;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Models\Project;
 | 
					use App\Models\Project;
 | 
				
			||||||
use App\Models\ProjectLanguage;
 | 
					 | 
				
			||||||
use App\ServiceResults\ServiceResult;
 | 
					use App\ServiceResults\ServiceResult;
 | 
				
			||||||
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class PagePossibleWithoutTranslation extends ServiceResult
 | 
					final class PagePossibleWithoutTranslation extends ServiceResult
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private readonly Project $project,
 | 
					        private readonly Project $project,
 | 
				
			||||||
        private readonly ProjectLanguage $language,
 | 
					        private readonly WebsiteTranslations $websiteTranslations,
 | 
				
			||||||
        private readonly array $data,
 | 
					        private readonly array $data,
 | 
				
			||||||
        private readonly bool  $isTranslation,
 | 
					        private readonly bool  $isTranslation,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
@@ -30,8 +30,8 @@ final class PagePossibleWithoutTranslation extends ServiceResult
 | 
				
			|||||||
        return $this->project;
 | 
					        return $this->project;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getLanguage(): ProjectLanguage
 | 
					    public function getWebsiteTranslations(): WebsiteTranslations
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->language;
 | 
					        return $this->websiteTranslations;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services\Admin\Project;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Service\Admin\Project\Translation\Translations;
 | 
				
			||||||
 | 
					use App\Dto\Service\Admin\Project\Translation\Update;
 | 
				
			||||||
 | 
					use App\Enums\CacheTag;
 | 
				
			||||||
 | 
					use App\Models\ProjectTranslation;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use App\Repositories\ProjectRepository;
 | 
				
			||||||
 | 
					use App\Repositories\ProjectTranslationRepository;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultArray;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultSuccess;
 | 
				
			||||||
 | 
					use App\Services\ClearCacheCommandHandler;
 | 
				
			||||||
 | 
					use App\Services\ProjectTranslation\ModelSyncCommand;
 | 
				
			||||||
 | 
					use App\Services\Service;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\DB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class TranslationService extends Service
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly ProjectRepository $projectRepository,
 | 
				
			||||||
 | 
					        private readonly ProjectTranslationRepository $projectTranslationRepository,
 | 
				
			||||||
 | 
					        private readonly ClearCacheCommandHandler $clearCacheCommandHandler,
 | 
				
			||||||
 | 
					        private readonly ModelSyncCommand $translationModelSyncCommand,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function languages(int $projectId, User $user): ServiceResultError | ServiceResultArray
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $project = $this->projectRepository->getProjectById($projectId);
 | 
				
			||||||
 | 
					        if (\is_null($project)) {
 | 
				
			||||||
 | 
					            return $this->errNotFound(__('Not Found'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($user->cannot('viewAny', ProjectTranslation::class)) {
 | 
				
			||||||
 | 
					            return $this->errFobidden(__('Access is denied'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->result([
 | 
				
			||||||
 | 
					            'project' => $project,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function edit(int $projectId, int $languageId, User $user): ServiceResultError | ServiceResultArray
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $project = $this->projectRepository->getProjectById($projectId);
 | 
				
			||||||
 | 
					        $language = $project?->languages()->firstWhere('id', $languageId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (\is_null($project) || \is_null($language)) {
 | 
				
			||||||
 | 
					            return $this->errNotFound(__('Not Found'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($user->cannot('viewAny', ProjectTranslation::class)) {
 | 
				
			||||||
 | 
					            return $this->errFobidden(__('Access is denied'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->result([
 | 
				
			||||||
 | 
					            'project' => $project,
 | 
				
			||||||
 | 
					            'language' => $language,
 | 
				
			||||||
 | 
					            'projectTranslations' => $this->projectTranslationRepository->getProjectTranslations($projectId, $languageId)->all()->pluck('text', 'code')->toArray(),
 | 
				
			||||||
 | 
					            'translations' => Translations::getTranslationCodes(),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function update(int $projectId, int $languageId, Update $data, User $user): ServiceResultError | ServiceResultSuccess
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $project = $this->projectRepository->getProjectById($projectId);
 | 
				
			||||||
 | 
					        $language = $project?->languages()->firstWhere('id', $languageId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (\is_null($project) || \is_null($language)) {
 | 
				
			||||||
 | 
					            return $this->errNotFound(__('Not Found'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($user->cannot('update', ProjectTranslation::class)) {
 | 
				
			||||||
 | 
					            return $this->errFobidden(__('Access is denied'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            DB::transaction(function () use ($data, $project, $language) {
 | 
				
			||||||
 | 
					                $this->translationModelSyncCommand->execute($project, $language, $data->getTranslations());
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            $this->clearCacheCommandHandler->byTag(CacheTag::ProjectTranslation);
 | 
				
			||||||
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
 | 
					            report($e);
 | 
				
			||||||
 | 
					            return $this->errService(__('Server Error'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->ok(__('Translations successfully updated'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -23,6 +23,8 @@ final readonly class ModelSyncCommand
 | 
				
			|||||||
                'title' => $language->getTitle(),
 | 
					                'title' => $language->getTitle(),
 | 
				
			||||||
                'sort' => $language->getSort(),
 | 
					                'sort' => $language->getSort(),
 | 
				
			||||||
                'is_default' => $language->isDefault(),
 | 
					                'is_default' => $language->isDefault(),
 | 
				
			||||||
 | 
					                'system_lang' => $language->getSystemLang(),
 | 
				
			||||||
 | 
					                'iso_code' => $language->getIsoCode(),
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if ($language->getId() !== null) {
 | 
					            if ($language->getId() !== null) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services\ProjectTranslation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Service\Admin\Project\Translation\Translation;
 | 
				
			||||||
 | 
					use App\Dto\Service\Admin\Project\Translation\Translations;
 | 
				
			||||||
 | 
					use App\Models\Project;
 | 
				
			||||||
 | 
					use App\Models\ProjectLanguage;
 | 
				
			||||||
 | 
					use App\Models\ProjectTranslation;
 | 
				
			||||||
 | 
					use App\Repositories\ProjectTranslationRepository;
 | 
				
			||||||
 | 
					use Illuminate\Support\Carbon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class ModelSyncCommand
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private ProjectTranslationRepository $projectTranslationRepository,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function execute(Project $project, ProjectLanguage $language, Translations $update): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $modelTranslations = $this->projectTranslationRepository->getProjectTranslationsWithTrashed($project->id, $language->id)->all();
 | 
				
			||||||
 | 
					        $insert = [];
 | 
				
			||||||
 | 
					        foreach ($update->getTranslations() as $translation) {
 | 
				
			||||||
 | 
					            /** @var Translation $translation */
 | 
				
			||||||
 | 
					            /** @var ProjectTranslation $modelTranslation */
 | 
				
			||||||
 | 
					            $modelTranslation = $modelTranslations->firstWhere('code', $translation->getCode());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($modelTranslation === null && $translation->getText() === null) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($modelTranslation === null && $translation->getText() !== null) {
 | 
				
			||||||
 | 
					                $insert[] = [
 | 
				
			||||||
 | 
					                    'created_at'  => Carbon::now(),
 | 
				
			||||||
 | 
					                    'updated_at'  => Carbon::now(),
 | 
				
			||||||
 | 
					                    'project_id'  => $project->id,
 | 
				
			||||||
 | 
					                    'language_id' => $language->id,
 | 
				
			||||||
 | 
					                    'code' => $translation->getCode(),
 | 
				
			||||||
 | 
					                    'text' => $translation->getText(),
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($modelTranslation !== null && $translation->getText() === null) {
 | 
				
			||||||
 | 
					                $modelTranslation->delete();
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($modelTranslation->trashed()) {
 | 
				
			||||||
 | 
					                $modelTranslation->deleted_at = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            $modelTranslation->text = $translation->getText();
 | 
				
			||||||
 | 
					            $modelTranslation->save();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!empty($insert)) {
 | 
				
			||||||
 | 
					            ProjectTranslation::query()->insert($insert);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,7 +3,6 @@
 | 
				
			|||||||
namespace App\Services;
 | 
					namespace App\Services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Models\Project;
 | 
					use App\Models\Project;
 | 
				
			||||||
use App\Models\ProjectLanguage;
 | 
					 | 
				
			||||||
use App\ServiceResults\ServiceResultArray;
 | 
					use App\ServiceResults\ServiceResultArray;
 | 
				
			||||||
use App\ServiceResults\ServiceResultError;
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
use App\ServiceResults\ServiceResultSuccess;
 | 
					use App\ServiceResults\ServiceResultSuccess;
 | 
				
			||||||
@@ -11,7 +10,6 @@ use App\ServiceResults\Site\PagePossibleWithoutTranslation;
 | 
				
			|||||||
use App\ServiceResults\StoreUpdateResult;
 | 
					use App\ServiceResults\StoreUpdateResult;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Model;
 | 
					use Illuminate\Database\Eloquent\Model;
 | 
				
			||||||
use Illuminate\Http\Response;
 | 
					use Illuminate\Http\Response;
 | 
				
			||||||
use Illuminate\Support\Facades\Cache;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class Service
 | 
					abstract class Service
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -60,9 +58,9 @@ abstract class Service
 | 
				
			|||||||
        return new ServiceResultArray(data: $data);
 | 
					        return new ServiceResultArray(data: $data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final protected function resultSitePage(Project $project, ProjectLanguage $language, array $data, bool $isTranslation): PagePossibleWithoutTranslation
 | 
					    final protected function resultSitePage(Project $project, WebsiteTranslations $websiteTranslations, array $data, bool $isTranslation): PagePossibleWithoutTranslation
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return new PagePossibleWithoutTranslation($project, $language, $data, $isTranslation);
 | 
					        return new PagePossibleWithoutTranslation($project, $websiteTranslations, $data, $isTranslation);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final protected function error(int $code, string $message, array $errors = []): ServiceResultError
 | 
					    final protected function error(int $code, string $message, array $errors = []): ServiceResultError
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,19 +6,18 @@ use App\Models\Project;
 | 
				
			|||||||
use App\Models\User;
 | 
					use App\Models\User;
 | 
				
			||||||
use App\Dto\Builder\Project as ProjectBuilderDto;
 | 
					use App\Dto\Builder\Project as ProjectBuilderDto;
 | 
				
			||||||
use App\Repositories\ProjectContentRepository;
 | 
					use App\Repositories\ProjectContentRepository;
 | 
				
			||||||
use App\Repositories\ProjectLanguageRepository;
 | 
					 | 
				
			||||||
use App\Repositories\ProjectLinkRepository;
 | 
					use App\Repositories\ProjectLinkRepository;
 | 
				
			||||||
use App\Repositories\ProjectRepository;
 | 
					use App\Repositories\ProjectRepository;
 | 
				
			||||||
use App\ServiceResults\ServiceResultArray;
 | 
					use App\ServiceResults\ServiceResultArray;
 | 
				
			||||||
use App\ServiceResults\ServiceResultError;
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
use App\ServiceResults\Site\PagePossibleWithoutTranslation;
 | 
					use App\ServiceResults\Site\PagePossibleWithoutTranslation;
 | 
				
			||||||
use App\Services\Service;
 | 
					use App\Services\Service;
 | 
				
			||||||
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class ProjectService extends Service
 | 
					final class ProjectService extends Service
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private readonly ProjectRepository $projectRepository,
 | 
					        private readonly ProjectRepository $projectRepository,
 | 
				
			||||||
        private readonly ProjectLanguageRepository $projectLanguageRepository,
 | 
					 | 
				
			||||||
        private readonly ProjectContentRepository $projectContentRepository,
 | 
					        private readonly ProjectContentRepository $projectContentRepository,
 | 
				
			||||||
        private readonly ProjectLinkRepository $projectLinkRepository,
 | 
					        private readonly ProjectLinkRepository $projectLinkRepository,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
@@ -39,7 +38,7 @@ final class ProjectService extends Service
 | 
				
			|||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getAboutByProject(Project $project, ?string $languageCode, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
 | 
					    public function getAboutByProject(Project $project, WebsiteTranslations $websiteTranslations, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            $project->is_public === false
 | 
					            $project->is_public === false
 | 
				
			||||||
@@ -47,17 +46,13 @@ final class ProjectService extends Service
 | 
				
			|||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            return $this->errFobidden(__('Access is denied'));
 | 
					            return $this->errFobidden(__('Access is denied'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        $language = $this->projectLanguageRepository->getProjectLanguageByCodeOrDefault($project, $languageCode);
 | 
					 | 
				
			||||||
        if (!$language) {
 | 
					 | 
				
			||||||
            return $this->errNotFound(__('Language not found'));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $data = [
 | 
					        $data = [
 | 
				
			||||||
            'project'  => $project,
 | 
					            'project'  => $project,
 | 
				
			||||||
            'language' => $language,
 | 
					            'websiteTranslations' => $websiteTranslations,
 | 
				
			||||||
            'content'  => $this->projectContentRepository->getContentByLanguageId($project->id, $language->id),
 | 
					            'content'  => $this->projectContentRepository->getContentByLanguageId($project->id, $websiteTranslations->getLanguage()->id),
 | 
				
			||||||
            'links'    => $this->projectLinkRepository->getLinksByProject($project, $language->id),
 | 
					            'links'    => $this->projectLinkRepository->getLinksByProject($project, $websiteTranslations->getLanguage()->id),
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
        return $this->resultSitePage($project, $language, $data, \is_null($data['content']));
 | 
					        return $this->resultSitePage($project, $websiteTranslations, $data, \is_null($data['content']));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								app/application/app/Services/WebsiteTranslations.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/application/app/Services/WebsiteTranslations.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\ProjectLanguage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class WebsiteTranslations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private ProjectLanguage $language,
 | 
				
			||||||
 | 
					        private array $transactions,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function translate(string $text): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->transactions[$text] ?? __($text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getLanguage(): ProjectLanguage
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->language;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,8 +2,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace app\View\Components\Site;
 | 
					namespace app\View\Components\Site;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Models\ProjectLanguage;
 | 
					use App\Enums\CacheTag;
 | 
				
			||||||
use Illuminate\Support\Collection;
 | 
					use App\Models\Project;
 | 
				
			||||||
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
use Illuminate\Support\Str;
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
use Illuminate\View\Component;
 | 
					use Illuminate\View\Component;
 | 
				
			||||||
use Illuminate\View\View;
 | 
					use Illuminate\View\View;
 | 
				
			||||||
@@ -11,20 +12,25 @@ use Illuminate\View\View;
 | 
				
			|||||||
final class ChooseLanguage extends Component
 | 
					final class ChooseLanguage extends Component
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private readonly ProjectLanguage $language,
 | 
					        private readonly WebsiteTranslations $websiteTranslations,
 | 
				
			||||||
        private readonly Collection $languages,
 | 
					        private readonly Project $project,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function render(): View
 | 
					    public function render(): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $link = Str::of( request()->getRequestUri() )->rtrim('/');
 | 
					        $link = Str::of( request()->url() )->rtrim('/');
 | 
				
			||||||
        if ($link->endsWith('/language/' . $this->language->code)) {
 | 
					        if ($link->endsWith('/language/' . $this->websiteTranslations->getLanguage()->code)) {
 | 
				
			||||||
            $link = $link->replace('/language/' . $this->language->code, '', false);
 | 
					            $link = $link->replace('/language/' . $this->websiteTranslations->getLanguage()->code, '', false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $seconds = 3600 * 12;
 | 
				
			||||||
 | 
					        $languages = CacheTag::Project->getCache()->remember(self::class . $this->project->id, $seconds, function () {
 | 
				
			||||||
 | 
					            return $this->project->languages;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return view('components.site.choose-language', [
 | 
					        return view('components.site.choose-language', [
 | 
				
			||||||
            'language'  => $this->language,
 | 
					            'websiteTranslations' => $this->websiteTranslations,
 | 
				
			||||||
            'languages' => $this->languages,
 | 
					            'languages' => $languages,
 | 
				
			||||||
            'link'      => (string) $link,
 | 
					            'link'      => (string) $link,
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ namespace app\View\Components\Site;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use App\Enums\StorageType;
 | 
					use App\Enums\StorageType;
 | 
				
			||||||
use App\Models\Project;
 | 
					use App\Models\Project;
 | 
				
			||||||
use App\Models\ProjectLanguage;
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
use Illuminate\View\Component;
 | 
					use Illuminate\View\Component;
 | 
				
			||||||
use Illuminate\View\View;
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,7 +12,7 @@ final class Layout extends Component
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private readonly Project $project,
 | 
					        private readonly Project $project,
 | 
				
			||||||
        private readonly ProjectLanguage $language,
 | 
					        private readonly WebsiteTranslations $websiteTranslations,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function render(): View
 | 
					    public function render(): View
 | 
				
			||||||
@@ -20,7 +20,7 @@ final class Layout extends Component
 | 
				
			|||||||
        return view('layout.site', [
 | 
					        return view('layout.site', [
 | 
				
			||||||
            'project'  => $this->project,
 | 
					            'project'  => $this->project,
 | 
				
			||||||
            'logo'     => $this->project->getStorageOne(StorageType::Logo),
 | 
					            'logo'     => $this->project->getStorageOne(StorageType::Logo),
 | 
				
			||||||
            'language' => $this->language,
 | 
					            'websiteTranslations' => $this->websiteTranslations,
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,12 +29,18 @@ final class Languages extends Form
 | 
				
			|||||||
            $default = (int) $default;
 | 
					            $default = (int) $default;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        foreach ($value['items'] as $index => $lang) {
 | 
					        foreach ($value['items'] as $index => $lang) {
 | 
				
			||||||
 | 
					            $systemLang = null;
 | 
				
			||||||
 | 
					            if (!empty($lang['system_lang'])) {
 | 
				
			||||||
 | 
					                $systemLang = (int) $lang['system_lang'];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            $langs[$index] = [
 | 
					            $langs[$index] = [
 | 
				
			||||||
                'title' => $lang['title'] ?? '',
 | 
					                'title'       => $lang['title'] ?? '',
 | 
				
			||||||
                'code' => $lang['code'] ?? '',
 | 
					                'code'        => $lang['code'] ?? '',
 | 
				
			||||||
                'is_default' => ($index === $default),
 | 
					                'is_default'  => ($index === $default),
 | 
				
			||||||
                'sort' => $lang['sort'] ?? '',
 | 
					                'sort'        => $lang['sort'] ?? '',
 | 
				
			||||||
                'id' => $lang['id'] ?? null,
 | 
					                'system_lang' => $systemLang,
 | 
				
			||||||
 | 
					                'iso_code'    => $lang['iso_code'] ?? null,
 | 
				
			||||||
 | 
					                'id'          => $lang['id'] ?? null,
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,11 +11,8 @@ return Application::configure(basePath: dirname(__DIR__))
 | 
				
			|||||||
        commands: __DIR__.'/../routes/console.php',
 | 
					        commands: __DIR__.'/../routes/console.php',
 | 
				
			||||||
        health: '/up',
 | 
					        health: '/up',
 | 
				
			||||||
        then: function () {
 | 
					        then: function () {
 | 
				
			||||||
            Route::middleware([\App\Http\Middleware\ProjectDomain::class])->group(base_path('routes/web-project.php'));
 | 
					            Route::middleware(['web', \App\Http\Middleware\ProjectDomainAndLanguage::class])->group(base_path('routes/web-project.php'));
 | 
				
			||||||
            Route::middleware([
 | 
					            Route::middleware(['web', \App\Http\Middleware\ProjectAndLanguage::class])
 | 
				
			||||||
                \App\Http\Middleware\ProjectDomain::class,
 | 
					 | 
				
			||||||
                \App\Http\Middleware\Project::class,
 | 
					 | 
				
			||||||
            ])
 | 
					 | 
				
			||||||
                ->prefix('project/{project}')
 | 
					                ->prefix('project/{project}')
 | 
				
			||||||
                ->as('project.')
 | 
					                ->as('project.')
 | 
				
			||||||
                ->group(base_path('routes/web-project.php'));
 | 
					                ->group(base_path('routes/web-project.php'));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Database\Migrations\Migration;
 | 
				
			||||||
 | 
					use Illuminate\Database\Schema\Blueprint;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return new class extends Migration
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Run the migrations.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function up(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::table('project_languages', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->unsignedInteger('system_lang')->nullable()->after('sort');
 | 
				
			||||||
 | 
					            $table->string('iso_code', 30)->nullable()->after('system_lang');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Reverse the migrations.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function down(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::table('project_languages', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->dropColumn('system_lang');
 | 
				
			||||||
 | 
					            $table->dropColumn('iso_code');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Database\Migrations\Migration;
 | 
				
			||||||
 | 
					use Illuminate\Database\Schema\Blueprint;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return new class extends Migration
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Run the migrations.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function up(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::create('project_translations', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->id();
 | 
				
			||||||
 | 
					            $table->string('code');
 | 
				
			||||||
 | 
					            $table->text('text');
 | 
				
			||||||
 | 
					            $table->unsignedBigInteger('project_id')->index();
 | 
				
			||||||
 | 
					            $table->foreign('project_id')->references('id')->on('projects');
 | 
				
			||||||
 | 
					            $table->unsignedBigInteger('language_id')->index();
 | 
				
			||||||
 | 
					            $table->foreign('language_id')->references('id')->on('project_languages');
 | 
				
			||||||
 | 
					            $table->timestamps();
 | 
				
			||||||
 | 
					            $table->softDeletes();
 | 
				
			||||||
 | 
					            $table->index(['project_id', 'language_id', 'deleted_at']);
 | 
				
			||||||
 | 
					            $table->unique(['project_id', 'language_id', 'code']);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Reverse the migrations.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function down(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::dropIfExists('project_translations');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -255,5 +255,6 @@
 | 
				
			|||||||
    "The link was successfully updated": "The link was successfully updated",
 | 
					    "The link was successfully updated": "The link was successfully updated",
 | 
				
			||||||
    "The link has been deleted": "The link has been deleted",
 | 
					    "The link has been deleted": "The link has been deleted",
 | 
				
			||||||
    "Language not found": "Language not found",
 | 
					    "Language not found": "Language not found",
 | 
				
			||||||
    "Project not found": "Project not found"
 | 
					    "Project not found": "Project not found",
 | 
				
			||||||
 | 
					    "Translations successfully updated": "Translations successfully updated"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,4 +11,5 @@ return [
 | 
				
			|||||||
    'Languages' => 'Languages',
 | 
					    'Languages' => 'Languages',
 | 
				
			||||||
    'Last update' => 'Last update',
 | 
					    'Last update' => 'Last update',
 | 
				
			||||||
    'Links project' => 'Links from the project',
 | 
					    'Links project' => 'Links from the project',
 | 
				
			||||||
 | 
					    'Translations' => 'Translations',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,4 +15,5 @@ return [
 | 
				
			|||||||
    'Project'        => 'Projects',
 | 
					    'Project'        => 'Projects',
 | 
				
			||||||
    'ProjectContent' => 'About the project',
 | 
					    'ProjectContent' => 'About the project',
 | 
				
			||||||
    'ProjectLink'    => 'Links from the project',
 | 
					    'ProjectLink'    => 'Links from the project',
 | 
				
			||||||
 | 
					    'ProjectTranslation' => 'Translations',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -281,11 +281,15 @@ return [
 | 
				
			|||||||
        'is_public'                => 'public',
 | 
					        'is_public'                => 'public',
 | 
				
			||||||
        'http_host'                => 'hostname',
 | 
					        'http_host'                => 'hostname',
 | 
				
			||||||
        'sort'                     => 'sorting',
 | 
					        'sort'                     => 'sorting',
 | 
				
			||||||
 | 
					        'system_lang'              => 'system language',
 | 
				
			||||||
 | 
					        'iso_code'                 => 'ISO code',
 | 
				
			||||||
        'is_default'               => 'default',
 | 
					        'is_default'               => 'default',
 | 
				
			||||||
        'language-code'            => 'language code',
 | 
					        'language-code'            => 'language code',
 | 
				
			||||||
        'languages.items.*.code'   => 'language code',
 | 
					        'languages.items.*.code'   => 'language code',
 | 
				
			||||||
        'languages.items.*.title'  => 'language title',
 | 
					        'languages.items.*.title'  => 'language title',
 | 
				
			||||||
        'languages.items.*.sort'   => 'language sorting',
 | 
					        'languages.items.*.sort'   => 'language sorting',
 | 
				
			||||||
 | 
					        'languages.items.*.system_lang' => 'system language',
 | 
				
			||||||
 | 
					        'languages.items.*.iso_code'    => 'ISO code',
 | 
				
			||||||
        'languages.items.*.id'     => 'language ID',
 | 
					        'languages.items.*.id'     => 'language ID',
 | 
				
			||||||
        'languages.default'        => 'default language',
 | 
					        'languages.default'        => 'default language',
 | 
				
			||||||
        'language-default'         => 'default language',
 | 
					        'language-default'         => 'default language',
 | 
				
			||||||
@@ -294,5 +298,8 @@ return [
 | 
				
			|||||||
        'logo.delete'              => 'remove logo',
 | 
					        'logo.delete'              => 'remove logo',
 | 
				
			||||||
        'link'                     => 'link',
 | 
					        'link'                     => 'link',
 | 
				
			||||||
        'language_id'              => 'language',
 | 
					        'language_id'              => 'language',
 | 
				
			||||||
 | 
					        'translations'             => 'translations',
 | 
				
			||||||
 | 
					        'translations.*.code'      => 'translations code',
 | 
				
			||||||
 | 
					        'translations.*.text'      => 'translations',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -255,5 +255,6 @@
 | 
				
			|||||||
    "The link was successfully updated": "Ссылка успешно обновлена",
 | 
					    "The link was successfully updated": "Ссылка успешно обновлена",
 | 
				
			||||||
    "The link has been deleted": "Ссылка удалена",
 | 
					    "The link has been deleted": "Ссылка удалена",
 | 
				
			||||||
    "Language not found": "Язык не найден",
 | 
					    "Language not found": "Язык не найден",
 | 
				
			||||||
    "Project not found": "Проект не найден"
 | 
					    "Project not found": "Проект не найден",
 | 
				
			||||||
 | 
					    "Translations successfully updated": "Переводы успешно обновлены"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,4 +11,5 @@ return [
 | 
				
			|||||||
    'Languages' => 'Языки',
 | 
					    'Languages' => 'Языки',
 | 
				
			||||||
    'Last update' => 'Последнее обновление',
 | 
					    'Last update' => 'Последнее обновление',
 | 
				
			||||||
    'Links project' => 'Ссылки от проекта',
 | 
					    'Links project' => 'Ссылки от проекта',
 | 
				
			||||||
 | 
					    'Translations' => 'Переводы',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,4 +15,5 @@ return [
 | 
				
			|||||||
    'Project'        => 'Проекты',
 | 
					    'Project'        => 'Проекты',
 | 
				
			||||||
    'ProjectContent' => 'О проекте',
 | 
					    'ProjectContent' => 'О проекте',
 | 
				
			||||||
    'ProjectLink'    => 'Ссылки от проекта',
 | 
					    'ProjectLink'    => 'Ссылки от проекта',
 | 
				
			||||||
 | 
					    'ProjectTranslation' => 'Переводы',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -282,10 +282,14 @@ return [
 | 
				
			|||||||
        'http_host'                => 'имя хоста',
 | 
					        'http_host'                => 'имя хоста',
 | 
				
			||||||
        'sort'                     => 'сортировка',
 | 
					        'sort'                     => 'сортировка',
 | 
				
			||||||
        'is_default'               => 'по умолчанию',
 | 
					        'is_default'               => 'по умолчанию',
 | 
				
			||||||
 | 
					        'system_lang'              => 'системный язык',
 | 
				
			||||||
 | 
					        'iso_code'                 => 'ISO-код',
 | 
				
			||||||
        'language-code'            => 'код языка',
 | 
					        'language-code'            => 'код языка',
 | 
				
			||||||
        'languages.items.*.code'   => 'код языка',
 | 
					        'languages.items.*.code'   => 'код языка',
 | 
				
			||||||
        'languages.items.*.title'  => 'заголовок языка',
 | 
					        'languages.items.*.title'  => 'заголовок языка',
 | 
				
			||||||
        'languages.items.*.sort'   => 'языковая сортировка',
 | 
					        'languages.items.*.sort'   => 'языковая сортировка',
 | 
				
			||||||
 | 
					        'languages.items.*.system_lang' => 'системный язык',
 | 
				
			||||||
 | 
					        'languages.items.*.iso_code'    => 'ISO-код',
 | 
				
			||||||
        'languages.items.*.id'     => 'ID языка',
 | 
					        'languages.items.*.id'     => 'ID языка',
 | 
				
			||||||
        'languages.default'        => 'язык по умолчанию',
 | 
					        'languages.default'        => 'язык по умолчанию',
 | 
				
			||||||
        'language-default'         => 'язык по умолчанию',
 | 
					        'language-default'         => 'язык по умолчанию',
 | 
				
			||||||
@@ -294,5 +298,8 @@ return [
 | 
				
			|||||||
        'logo.delete'              => 'удалить логотип',
 | 
					        'logo.delete'              => 'удалить логотип',
 | 
				
			||||||
        'link'                     => 'ссылка',
 | 
					        'link'                     => 'ссылка',
 | 
				
			||||||
        'language_id'              => 'язык',
 | 
					        'language_id'              => 'язык',
 | 
				
			||||||
 | 
					        'translations'             => 'переводы',
 | 
				
			||||||
 | 
					        'translations.*.code'      => 'код перевода',
 | 
				
			||||||
 | 
					        'translations.*.text'      => 'переводы',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@ body {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
.header_logo {
 | 
					.header_logo {
 | 
				
			||||||
    grid-area: logo;
 | 
					    grid-area: logo;
 | 
				
			||||||
    max-height: 50px;
 | 
					    height: 50px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    a {
 | 
					    a {
 | 
				
			||||||
        text-decoration: none;
 | 
					        text-decoration: none;
 | 
				
			||||||
@@ -67,6 +67,7 @@ body {
 | 
				
			|||||||
        padding: 5px 10px;
 | 
					        padding: 5px 10px;
 | 
				
			||||||
        border: 1px solid #0a0e17;
 | 
					        border: 1px solid #0a0e17;
 | 
				
			||||||
        border-radius: 4px;
 | 
					        border-radius: 4px;
 | 
				
			||||||
 | 
					        white-space: nowrap;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#mobile-menu {
 | 
					#mobile-menu {
 | 
				
			||||||
@@ -231,12 +232,24 @@ body.mobile-menu-open {
 | 
				
			|||||||
        grid-template-areas: "logo language";
 | 
					        grid-template-areas: "logo language";
 | 
				
			||||||
        grid-template-columns: 1fr 200px;
 | 
					        grid-template-columns: 1fr 200px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    .header_logo {
 | 
				
			||||||
 | 
					        width: 150px;
 | 
				
			||||||
 | 
					        margin-left: 12px;
 | 
				
			||||||
 | 
					        margin-top: 10px;
 | 
				
			||||||
 | 
					        margin-bottom: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    #mobile-menu {
 | 
					    #mobile-menu {
 | 
				
			||||||
        display: none;
 | 
					        display: none;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    .main-container {
 | 
					    .main-container {
 | 
				
			||||||
        flex-direction: row;
 | 
					        flex-direction: row;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    #language {
 | 
				
			||||||
 | 
					        margin-right: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .language__block {
 | 
				
			||||||
 | 
					        margin-top: 6px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    .language__button {
 | 
					    .language__button {
 | 
				
			||||||
        padding: 7px 10px 7px 0;
 | 
					        padding: 7px 10px 7px 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
@section('meta_title', __('admin-sections.Links project'))
 | 
					@section('meta_title', __('admin-sections.Links project'))
 | 
				
			||||||
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
 | 
					@section('h1', __('admin-sections.Project') . ': ' . $project->name)
 | 
				
			||||||
<x-admin.layout>
 | 
					<x-admin.layout>
 | 
				
			||||||
    @include('admin.projects._top')
 | 
					    @include('admin.projects.links._top')
 | 
				
			||||||
    <div class="row">
 | 
					    <div class="row">
 | 
				
			||||||
        <div class="col-12 mb-4">
 | 
					        <div class="col-12 mb-4">
 | 
				
			||||||
            <div class="card border-0 shadow components-section">
 | 
					            <div class="card border-0 shadow components-section">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,18 @@
 | 
				
			|||||||
                                </td>
 | 
					                                </td>
 | 
				
			||||||
                            </tr>
 | 
					                            </tr>
 | 
				
			||||||
                        @endcan
 | 
					                        @endcan
 | 
				
			||||||
 | 
					                        @can('viewAny', \App\Models\ProjectTranslation::class)
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    <a href="{{ route('admin.projects.translations.languages', ['project' => $project->id]) }}" class="fw-bold">
 | 
				
			||||||
 | 
					                                        <svg width="16" height="16" data-slot="icon" class="align-text-top" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
				
			||||||
 | 
					                                            <path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802"></path>
 | 
				
			||||||
 | 
					                                        </svg>
 | 
				
			||||||
 | 
					                                        {{ __('admin-sections.Translations') }}
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                        @endcan
 | 
				
			||||||
                    </tbody>
 | 
					                    </tbody>
 | 
				
			||||||
                </table>
 | 
					                </table>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					@section('meta_title', __('admin-sections.Translations'))
 | 
				
			||||||
 | 
					@section('h1', __('admin-sections.Project') . ': ' . $project->name)
 | 
				
			||||||
 | 
					<x-admin.layout>
 | 
				
			||||||
 | 
					    <div class="row">
 | 
				
			||||||
 | 
					        <div class="col-12 mb-4">
 | 
				
			||||||
 | 
					            <div class="card border-0 shadow components-section">
 | 
				
			||||||
 | 
					                <div class="card-body">
 | 
				
			||||||
 | 
					                    <h3 id="category" class="mb-4">{{ __('admin-sections.Translations') }}</h3>
 | 
				
			||||||
 | 
					                    <div class="table-responsive">
 | 
				
			||||||
 | 
					                        <form method="post" action="{{ route('admin.projects.translations.update', ['project' => $project->id, 'language' => $language->id]) }}">
 | 
				
			||||||
 | 
					                            <table class="table table-centered table-nowrap mb-0 rounded">
 | 
				
			||||||
 | 
					                                <thead class="thead-light">
 | 
				
			||||||
 | 
					                                <tr>
 | 
				
			||||||
 | 
					                                    <th class="border-0" style="width: 250px;">{{ __('validation.attributes.text') }}</th>
 | 
				
			||||||
 | 
					                                    <th class="border-0">{{ __('admin-sections.Translations') }}</th>
 | 
				
			||||||
 | 
					                                </tr>
 | 
				
			||||||
 | 
					                                </thead>
 | 
				
			||||||
 | 
					                                <tbody>
 | 
				
			||||||
 | 
					                                @foreach($translations as $index => $code)
 | 
				
			||||||
 | 
					                                    <tr>
 | 
				
			||||||
 | 
					                                        <td>
 | 
				
			||||||
 | 
					                                            {{ __($code) }}
 | 
				
			||||||
 | 
					                                        </td>
 | 
				
			||||||
 | 
					                                        <td>
 | 
				
			||||||
 | 
					                                            <x-volt.forms.input-type-hidden :name="'translations[' . $index . '][code]'" :value="$code" />
 | 
				
			||||||
 | 
					                                            <x-volt.forms.input title="" :name="'translations[' . $index . '][text]'" :value="$projectTranslations[$code] ?? null" />
 | 
				
			||||||
 | 
					                                        </td>
 | 
				
			||||||
 | 
					                                    </tr>
 | 
				
			||||||
 | 
					                                @endforeach
 | 
				
			||||||
 | 
					                                </tbody>
 | 
				
			||||||
 | 
					                            </table>
 | 
				
			||||||
 | 
					                            @canany(['update'], \App\Models\ProjectTranslation::class)
 | 
				
			||||||
 | 
					                                @csrf
 | 
				
			||||||
 | 
					                                <button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
 | 
				
			||||||
 | 
					                            @endcanany
 | 
				
			||||||
 | 
					                        </form>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</x-admin.layout>
 | 
				
			||||||
@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					@section('meta_title', __('admin-sections.Translations'))
 | 
				
			||||||
 | 
					@section('h1', __('admin-sections.Project') . ': ' . $project->name)
 | 
				
			||||||
 | 
					<x-admin.layout>
 | 
				
			||||||
 | 
					    <div class="row">
 | 
				
			||||||
 | 
					        <div class="col-12 mb-4">
 | 
				
			||||||
 | 
					            <div class="card border-0 shadow components-section">
 | 
				
			||||||
 | 
					                <div class="card-body">
 | 
				
			||||||
 | 
					                    <h3 id="category" class="mb-4">{{ __('admin-sections.Translations') }}</h3>
 | 
				
			||||||
 | 
					                    <div class="table-responsive">
 | 
				
			||||||
 | 
					                        <table class="table table-centered table-nowrap mb-0 rounded">
 | 
				
			||||||
 | 
					                            <thead class="thead-light">
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <th class="border-0">{{ __('admin-sections.Languages') }}</th>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                            </thead>
 | 
				
			||||||
 | 
					                            <tbody>
 | 
				
			||||||
 | 
					                            @foreach($project->languages as $language)
 | 
				
			||||||
 | 
					                                <tr>
 | 
				
			||||||
 | 
					                                    <td>
 | 
				
			||||||
 | 
					                                        <a href="{{ route('admin.projects.translations.edit', ['project' => $project->id, 'language' => $language->id]) }}" class="fw-bold">
 | 
				
			||||||
 | 
					                                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16">
 | 
				
			||||||
 | 
					                                                <path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
 | 
				
			||||||
 | 
					                                            </svg>
 | 
				
			||||||
 | 
					                                            {{ $language->title }}
 | 
				
			||||||
 | 
					                                        </a>
 | 
				
			||||||
 | 
					                                    </td>
 | 
				
			||||||
 | 
					                                </tr>
 | 
				
			||||||
 | 
					                            @endforeach
 | 
				
			||||||
 | 
					                            </tbody>
 | 
				
			||||||
 | 
					                        </table>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</x-admin.layout>
 | 
				
			||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
<div id="language">
 | 
					<div id="language">
 | 
				
			||||||
    <div class="language__block">
 | 
					    <div class="language__block">
 | 
				
			||||||
        <button class="language__button" type="button" aria-label="{{ __('site.Choose language') }}">
 | 
					        <button class="language__button" type="button" aria-label="{{ $websiteTranslations->translate('site.Choose language') }}">
 | 
				
			||||||
            <svg width="16" height="16" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
					            <svg width="16" height="16" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
				
			||||||
                <path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802"></path>
 | 
					                <path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802"></path>
 | 
				
			||||||
            </svg>
 | 
					            </svg>
 | 
				
			||||||
            {{ $language->title }}
 | 
					            {{ $websiteTranslations->getLanguage()->title }}
 | 
				
			||||||
            <svg width="16" height="16" class="language__button__str" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
					            <svg width="16" height="16" class="language__button__str" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
				
			||||||
                <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
 | 
					                <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
 | 
				
			||||||
            </svg>
 | 
					            </svg>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
<div class="mb-4">
 | 
					<div class="mb-4">
 | 
				
			||||||
    <label for="form-input-{{ $requestName }}">{{ $title }}
 | 
					    @if(!empty($title) || !empty($example))
 | 
				
			||||||
        @if(!empty($example))
 | 
					        <label for="form-input-{{ $requestName }}">{{ $title }}
 | 
				
			||||||
            <span class="label__example">({{ __('example:') }} {!! $example !!})</span>
 | 
					            @if(!empty($example))
 | 
				
			||||||
        @endif
 | 
					                <span class="label__example">({{ __('example:') }} {!! $example !!})</span>
 | 
				
			||||||
    </label>
 | 
					            @endif
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					    @endif
 | 
				
			||||||
    <input id="form-input-{{ $requestName }}" class="form-control @error($requestName) is-invalid @enderror" name="{{ $name }}" type="{{ $type }}" @if($type !== 'password') value="{{ $value }}" @endif {{ $attributes }}>
 | 
					    <input id="form-input-{{ $requestName }}" class="form-control @error($requestName) is-invalid @enderror" name="{{ $name }}" type="{{ $type }}" @if($type !== 'password') value="{{ $value }}" @endif {{ $attributes }}>
 | 
				
			||||||
    @error($requestName)
 | 
					    @error($requestName)
 | 
				
			||||||
        <span class="invalid-feedback">{{ $message }}</span>
 | 
					        <span class="invalid-feedback">{{ $message }}</span>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,10 @@
 | 
				
			|||||||
            <thead class="thead-light">
 | 
					            <thead class="thead-light">
 | 
				
			||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <th class="border-0 rounded-start">{{ __('validation.attributes.title') }}</th>
 | 
					                    <th class="border-0 rounded-start">{{ __('validation.attributes.title') }}</th>
 | 
				
			||||||
                    <th class="border-0">{{ __('validation.attributes.code') }}</th>
 | 
					                    <th class="border-0" style="width: 200px;">{{ __('validation.attributes.code') }}</th>
 | 
				
			||||||
                    <th class="border-0">{{ __('validation.attributes.sort') }}</th>
 | 
					                    <th class="border-0" style="width: 140px;">{{ __('validation.attributes.sort') }}</th>
 | 
				
			||||||
 | 
					                    <th class="border-0" style="width: 120px;">{{ __('validation.attributes.iso_code') }}</th>
 | 
				
			||||||
 | 
					                    <th class="border-0">{{ __('validation.attributes.system_lang') }}</th>
 | 
				
			||||||
                    <th class="border-0">{{ __('validation.attributes.is_default') }}</th>
 | 
					                    <th class="border-0">{{ __('validation.attributes.is_default') }}</th>
 | 
				
			||||||
                    <th class="border-0 rounded-end"></th>
 | 
					                    <th class="border-0 rounded-end"></th>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
<input class="form-control @error(\App\Helpers\Helpers::formatAttributeNameToRequestName($name)) is-invalid @enderror" name="{{ $name }}" type="{{ $type }}" value="{{ $value }}" required>
 | 
					<input class="form-control @error(\App\Helpers\Helpers::formatAttributeNameToRequestName($name)) is-invalid @enderror" name="{{ $name }}" type="{{ $type }}" value="{{ $value }}" @if($required) required @endif>
 | 
				
			||||||
@error(\App\Helpers\Helpers::formatAttributeNameToRequestName($name))
 | 
					@error(\App\Helpers\Helpers::formatAttributeNameToRequestName($name))
 | 
				
			||||||
    <span class="invalid-feedback">{{ $message }}</span>
 | 
					    <span class="invalid-feedback">{{ $message }}</span>
 | 
				
			||||||
@enderror
 | 
					@enderror
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,23 +3,40 @@
 | 
				
			|||||||
        <input type="hidden" name="{{ $name . '[items][' .$index . '][id]' }}" value="{{ $lang['id'] }}">
 | 
					        <input type="hidden" name="{{ $name . '[items][' .$index . '][id]' }}" value="{{ $lang['id'] }}">
 | 
				
			||||||
    @endif
 | 
					    @endif
 | 
				
			||||||
    @include('components.volt.forms.languages.input', [
 | 
					    @include('components.volt.forms.languages.input', [
 | 
				
			||||||
        'value' => $lang['title'],
 | 
					        'value'    => $lang['title'],
 | 
				
			||||||
        'name'  => $name . '[items][' .$index . '][title]',
 | 
					        'name'     => $name . '[items][' .$index . '][title]',
 | 
				
			||||||
        'type'  => 'text',
 | 
					        'type'     => 'text',
 | 
				
			||||||
 | 
					        'required' => true,
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
</td>
 | 
					</td>
 | 
				
			||||||
<td>
 | 
					<td>
 | 
				
			||||||
    @include('components.volt.forms.languages.input', [
 | 
					    @include('components.volt.forms.languages.input', [
 | 
				
			||||||
        'value' => $lang['code'],
 | 
					        'value'    => $lang['code'],
 | 
				
			||||||
        'name'  => $name . '[items][' . $index . '][code]',
 | 
					        'name'     => $name . '[items][' . $index . '][code]',
 | 
				
			||||||
        'type'  => 'text',
 | 
					        'type'     => 'text',
 | 
				
			||||||
 | 
					        'required' => true,
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
</td>
 | 
					</td>
 | 
				
			||||||
<td>
 | 
					<td>
 | 
				
			||||||
    @include('components.volt.forms.languages.input', [
 | 
					    @include('components.volt.forms.languages.input', [
 | 
				
			||||||
        'value' => $lang['sort'],
 | 
					        'value'    => $lang['sort'],
 | 
				
			||||||
        'name'  => $name . '[items][' . $index . '][sort]',
 | 
					        'name'     => $name . '[items][' . $index . '][sort]',
 | 
				
			||||||
        'type'  => 'number',
 | 
					        'type'     => 'number',
 | 
				
			||||||
 | 
					        'required' => true,
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					</td>
 | 
				
			||||||
 | 
					<td>
 | 
				
			||||||
 | 
					    @include('components.volt.forms.languages.input', [
 | 
				
			||||||
 | 
					        'value'    => $lang['iso_code'] ?? null,
 | 
				
			||||||
 | 
					        'name'     => $name . '[items][' . $index . '][iso_code]',
 | 
				
			||||||
 | 
					        'type'     => 'text',
 | 
				
			||||||
 | 
					        'required' => false,
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					</td>
 | 
				
			||||||
 | 
					<td>
 | 
				
			||||||
 | 
					    @include('components.volt.forms.languages.system_lang', [
 | 
				
			||||||
 | 
					        'value' => $lang['system_lang'] ?? null,
 | 
				
			||||||
 | 
					        'name'  => $name . '[items][' . $index . '][system_lang]',
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
</td>
 | 
					</td>
 | 
				
			||||||
<td class="align-middle">
 | 
					<td class="align-middle">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<select class="form-select @error(\App\Helpers\Helpers::formatAttributeNameToRequestName($name)) is-invalid @enderror" name="{{ $name }}">
 | 
				
			||||||
 | 
					    <option value=""></option>
 | 
				
			||||||
 | 
					    @foreach(\App\Enums\Lang::cases() as $lang)
 | 
				
			||||||
 | 
					        <option @if($lang->value === $value) selected @endif value="{{ $lang->value }}">{{ $lang->getTitle() }}</option>
 | 
				
			||||||
 | 
					    @endforeach
 | 
				
			||||||
 | 
					</select>
 | 
				
			||||||
 | 
					@error(\App\Helpers\Helpers::formatAttributeNameToRequestName($name))
 | 
				
			||||||
 | 
					    <span class="invalid-feedback">{{ $message }}</span>
 | 
				
			||||||
 | 
					@enderror
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					<!DOCTYPE html>
 | 
				
			||||||
<html lang="{{ $language->code }}">
 | 
					<html lang="{{ $websiteTranslations->getLanguage()->attribute_lang }}">
 | 
				
			||||||
<head>
 | 
					<head>
 | 
				
			||||||
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 | 
					    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 | 
				
			||||||
@@ -14,7 +14,7 @@
 | 
				
			|||||||
    <div class="wrapper">
 | 
					    <div class="wrapper">
 | 
				
			||||||
        <header class="header">
 | 
					        <header class="header">
 | 
				
			||||||
            <div class="header_logo">
 | 
					            <div class="header_logo">
 | 
				
			||||||
                <a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $language) }}">
 | 
					                <a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $websiteTranslations->getLanguage()) }}">
 | 
				
			||||||
                    @if($logo)
 | 
					                    @if($logo)
 | 
				
			||||||
                        <img src="{{ $logo->url }}" alt="{{ $project->name }}">
 | 
					                        <img src="{{ $logo->url }}" alt="{{ $project->name }}">
 | 
				
			||||||
                    @else
 | 
					                    @else
 | 
				
			||||||
@@ -22,7 +22,7 @@
 | 
				
			|||||||
                    @endif
 | 
					                    @endif
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <button id="mobile-menu" type="button" aria-label="{{ __('site.Menu') }}">
 | 
					            <button id="mobile-menu" type="button" aria-label="{{ $websiteTranslations->translate('site.Menu') }}">
 | 
				
			||||||
                <svg class="open" width="45" height="45" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
					                <svg class="open" width="45" height="45" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
				
			||||||
                    <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"></path>
 | 
					                    <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"></path>
 | 
				
			||||||
                </svg>
 | 
					                </svg>
 | 
				
			||||||
@@ -30,13 +30,13 @@
 | 
				
			|||||||
                    <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
 | 
					                    <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
 | 
				
			||||||
                </svg>
 | 
					                </svg>
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
            <x-site.choose-language :language="$language" :languages="$project->languages" />
 | 
					            <x-site.choose-language :websiteTranslations="$websiteTranslations" :project="$project" />
 | 
				
			||||||
        </header>
 | 
					        </header>
 | 
				
			||||||
        <div class="main-container">
 | 
					        <div class="main-container">
 | 
				
			||||||
            <nav id="menu">
 | 
					            <nav id="menu">
 | 
				
			||||||
                <div class="menu__title">{{ __('site.Menu') }}</div>
 | 
					                <div class="menu__title">{{ __('site.Menu') }}</div>
 | 
				
			||||||
                <ul>
 | 
					                <ul>
 | 
				
			||||||
                    <li><a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $language) }}" @class(['active' => request()->route()->named(['home', 'language.home'])])>{{ __('site.About project') }}</a></li>
 | 
					                    <li><a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $websiteTranslations->getLanguage()) }}" @class(['active' => request()->route()->named(['home', 'language.home', 'project.home', 'project.language.home'])])>{{ $websiteTranslations->translate('site.About project') }}</a></li>
 | 
				
			||||||
                </ul>
 | 
					                </ul>
 | 
				
			||||||
            </nav>
 | 
					            </nav>
 | 
				
			||||||
            <div class="section-container">
 | 
					            <div class="section-container">
 | 
				
			||||||
@@ -45,7 +45,7 @@
 | 
				
			|||||||
                    {{ $slot }}
 | 
					                    {{ $slot }}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <footer class="footer">
 | 
					                <footer class="footer">
 | 
				
			||||||
                    <a href="https://git.kor-elf.net/kor-elf/my-projects-website" target="_blank">{{ __('site.Powered by service') }}</a>
 | 
					                    <a href="https://git.kor-elf.net/kor-elf/my-projects-website" target="_blank">{{ $websiteTranslations->translate('site.Powered by service') }}</a>
 | 
				
			||||||
                </footer>
 | 
					                </footer>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
@section('h1', __('site.Page without translation'))
 | 
					@section('h1', $websiteTranslations->translate('site.Page without translation'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<x-site.layout :project="$project" :language="$language">
 | 
					<x-site.layout :project="$project" :websiteTranslations="$websiteTranslations">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</x-site.layout>
 | 
					</x-site.layout>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
@section('meta_title', __('site.Project') . ': ' . $content->title)
 | 
					@section('meta_title', $websiteTranslations->translate('site.Project') . ': ' . $content->title)
 | 
				
			||||||
@section('h1', $content->title)
 | 
					@section('h1', $content->title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<x-site.layout :project="$project" :language="$language">
 | 
					<x-site.layout :project="$project" :websiteTranslations="$websiteTranslations">
 | 
				
			||||||
    {!! $content->description !!}
 | 
					    {!! $content->description !!}
 | 
				
			||||||
</x-site.layout>
 | 
					</x-site.layout>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,10 @@ Route::middleware(['auth', 'verified', \App\Http\Middleware\UserLocale::class])-
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            Route::resource('links', \App\Http\Controllers\Admin\Projects\LinksController::class)->except(['show'])->where(['link' => '[0-9]+']);
 | 
					            Route::resource('links', \App\Http\Controllers\Admin\Projects\LinksController::class)->except(['show'])->where(['link' => '[0-9]+']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Route::get('translations', [\App\Http\Controllers\Admin\Projects\TranslationsController::class, 'languages'])->name('translations.languages');
 | 
				
			||||||
 | 
					            Route::get('translations/{language}', [\App\Http\Controllers\Admin\Projects\TranslationsController::class, 'edit'])->name('translations.edit')->where(['language' => '[0-9]+']);
 | 
				
			||||||
 | 
					            Route::post('translations/{language}', [\App\Http\Controllers\Admin\Projects\TranslationsController::class, 'update'])->name('translations.update')->where(['language' => '[0-9]+']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        })->where(['project' => '[0-9]+']);
 | 
					        })->where(['project' => '[0-9]+']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Route::post('languages/new-language', [\App\Http\Controllers\Admin\LanguagesController::class, 'newLanguage'])->name('new-language');
 | 
					        Route::post('languages/new-language', [\App\Http\Controllers\Admin\LanguagesController::class, 'newLanguage'])->name('new-language');
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user