diff --git a/app/application/app/Dto/Service/Admin/Project/About/StoreUpdate.php b/app/application/app/Dto/Service/Admin/Project/About/StoreUpdate.php
index 3ac74b1..2f8094d 100644
--- a/app/application/app/Dto/Service/Admin/Project/About/StoreUpdate.php
+++ b/app/application/app/Dto/Service/Admin/Project/About/StoreUpdate.php
@@ -11,6 +11,7 @@ final readonly class StoreUpdate extends Dto
private string $title,
private string $description,
private Storages $storages,
+ private bool $isTranslateAutomatically = false,
) { }
public function getTitle(): string
@@ -27,4 +28,9 @@ final readonly class StoreUpdate extends Dto
{
return $this->storages;
}
+
+ public function isTranslateAutomatically(): bool
+ {
+ return $this->isTranslateAutomatically;
+ }
}
diff --git a/app/application/app/Http/Requests/Admin/Projects/About/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/About/StoreUpdateRequest.php
index abc6556..55394c2 100644
--- a/app/application/app/Http/Requests/Admin/Projects/About/StoreUpdateRequest.php
+++ b/app/application/app/Http/Requests/Admin/Projects/About/StoreUpdateRequest.php
@@ -26,6 +26,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
'title' => ['required', 'string', 'max:255',],
'description' => ['nullable', 'string',],
'storage.content_images.*.file' => ['numeric', 'min:1'],
+ 'translate-automatically' => ['nullable', 'boolean'],
];
}
@@ -35,6 +36,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
title: $this->input('title'),
description: $this->input('description'),
storages: $this->storages(),
+ isTranslateAutomatically: (bool) $this->input('translate-automatically', false),
);
}
diff --git a/app/application/app/Jobs/Translate/ProcessProjectContent.php b/app/application/app/Jobs/Translate/ProcessProjectContent.php
new file mode 100644
index 0000000..a6d74c4
--- /dev/null
+++ b/app/application/app/Jobs/Translate/ProcessProjectContent.php
@@ -0,0 +1,58 @@
+
+ */
+ public function middleware(): array
+ {
+ return [
+ (new WithoutOverlapping($this->uniqueId()))->expireAfter(180),
+ ];
+ }
+
+ /**
+ * Create a new job instance.
+ */
+ public function __construct(
+ private readonly int $projectId,
+ private readonly array $contentIds = [],
+ private readonly array $exceptLanguages = [],
+ ) { }
+
+ /**
+ * Get the unique ID for the job.
+ */
+ public function uniqueId(): string
+ {
+ return 'project-' . $this->projectId;
+ }
+
+ /**
+ * Execute the job.
+ * @throws Exception
+ */
+ public function handle(ProjectContentService $projectContentService): void
+ {
+ $result = $projectContentService->translate($this->projectId, $this->contentIds, $this->exceptLanguages);
+ if ($result->isError() && $result->getCode() !== 404) {
+ cache()->lock($this->uniqueId())->forceRelease();
+ throw new Exception($result->getMessage());
+ }
+ }
+}
diff --git a/app/application/app/Models/ProjectContent.php b/app/application/app/Models/ProjectContent.php
index 71644e7..ec9a35b 100644
--- a/app/application/app/Models/ProjectContent.php
+++ b/app/application/app/Models/ProjectContent.php
@@ -17,5 +17,6 @@ final class ProjectContent extends Model implements StorageContract
protected $fillable = [
'title',
'description',
+ 'language_id',
];
}
diff --git a/app/application/app/Services/Admin/Project/AboutService.php b/app/application/app/Services/Admin/Project/AboutService.php
index 611a99d..30b126e 100644
--- a/app/application/app/Services/Admin/Project/AboutService.php
+++ b/app/application/app/Services/Admin/Project/AboutService.php
@@ -5,6 +5,7 @@ namespace App\Services\Admin\Project;
use App\Contracts\ServiceResultError;
use App\Dto\Service\Admin\Project\About\StoreUpdate;
use App\Enums\Morph;
+use App\Jobs\Translate\ProcessProjectContent;
use App\Models\ProjectContent;
use App\Models\User;
use App\Repositories\ProjectContentRepository;
@@ -69,6 +70,7 @@ final class AboutService extends Service
'project' => $project,
'language' => $language,
'content' => $content,
+ 'serviceTranslationEnable' => config('translation_service.enable', false),
]);
}
@@ -105,6 +107,9 @@ final class AboutService extends Service
$this->storageService->saveAndDelete($aboutProject, $storages);
return $aboutProject;
});
+ if (\config('translation_service.enable', false)) {
+ $this->translateContent($aboutProject, $data);
+ }
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
@@ -133,6 +138,9 @@ final class AboutService extends Service
return $aboutProject;
});
+ if (\config('translation_service.enable', false)) {
+ $this->translateContent($aboutProject, $data);
+ }
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
@@ -148,4 +156,15 @@ final class AboutService extends Service
'description' => $data->getDescription(),
];
}
+
+ private function translateContent(ProjectContent $projectContent, StoreUpdate $data): void
+ {
+ if (! $data->isTranslateAutomatically()) {
+ return;
+ }
+ $translateExceptLanguages = [$projectContent->language_id];
+ $translateContentIds = [$projectContent->id];
+
+ ProcessProjectContent::dispatch($projectContent->project_id, $translateContentIds, $translateExceptLanguages);
+ }
}
diff --git a/app/application/app/Services/Translate/Completed/Project/ProjectContentService.php b/app/application/app/Services/Translate/Completed/Project/ProjectContentService.php
new file mode 100644
index 0000000..b9400e2
--- /dev/null
+++ b/app/application/app/Services/Translate/Completed/Project/ProjectContentService.php
@@ -0,0 +1,89 @@
+projectRepository->getProjectById((int) $data['projectId']);
+ if ($project === null) {
+ return;
+ }
+
+ $projectContent = DB::transaction(function () use ($data, $project, $translatedText) {
+ $values = [];
+ $hashes = $this->completionChecker->execute(
+ $this->getHashes($data['hashes']),
+ );
+
+ foreach ($translatedText as $translatedTextKey => $translatedTextValue) {
+ if ($hashes->isStatusWaiting($translatedTextKey) !== true) {
+ continue;
+ }
+
+ $translatedTextValue = $this->noTranslateAttributeHandler->handleRemoveAttribute($translatedTextValue);
+ $values[$translatedTextKey] = $translatedTextValue;
+ }
+ if (\count($values) === 0) {
+ return null;
+ }
+
+ $projectContent = $project->contents()->where('language_id', $data['languageId'])->first();
+ if ($projectContent !== null) {
+ $projectContent->update($values);
+ return $projectContent;
+ }
+
+ $values['language_id'] = $data['languageId'];
+ return $project->contents()->create($values);
+ });
+ if (\config('translation_service.enable', false) && $projectContent !== null) {
+ $this->translateContent($project, $projectContent, $data);
+ }
+ }
+
+ private function translateContent(Project $project, ProjectContent $projectContent, array $data): void
+ {
+ $translateExceptLanguages = $data['exceptLanguages'] ?? [];
+ $translateExceptLanguages[] = $data['languageId'];
+ ProcessProjectContent::dispatch($project->id, [$projectContent->id], $translateExceptLanguages);
+ }
+
+ private function getHashes(array $hashes): Hashes
+ {
+ $hashesDto = new Hashes();
+ foreach ($hashes as $field => $hash) {
+ $hashesDto->add((int) $hash['hashId'], $field, $hash['hash']);
+ }
+ return $hashesDto;
+ }
+}
diff --git a/app/application/app/Services/Translate/Project/ProjectContentService.php b/app/application/app/Services/Translate/Project/ProjectContentService.php
new file mode 100644
index 0000000..8a4266c
--- /dev/null
+++ b/app/application/app/Services/Translate/Project/ProjectContentService.php
@@ -0,0 +1,95 @@
+projectRepository->getProjectById($projectId);
+ if ($project === null) {
+ return $this->errNotFound(__('Project not found'));
+ }
+
+ $contents = $project->contents()
+ ->whereIn('id', $contentIds)
+ ->cursor();
+
+ $sourceLanguagesCode = [];
+ foreach ($contents as $content) {
+ if (!isset($sourceLanguagesCode[$content->language_id])) {
+ $sourceLanguagesCode[$content->language_id] = $this->projectTranslationServiceRepository->getLanguageCodeByLanguageId($content->language_id) ?? 'null';
+ }
+ $translateIntoLanguage = $this->projectTranslationServiceRepository
+ ->getLanguagesBySourceLanguage($content->language_id, $exceptLanguages)
+ ->all()->pluck('code', 'language_id')->toArray();
+
+ if (empty($translateIntoLanguage)) {
+ continue;
+ }
+
+ $fields = (new Fields())
+ ->add('title', $content->title ?? '')
+ ->add('description', $content->description ?? '');
+ $translations = DB::transaction(function () use ($content, $translateIntoLanguage, $fields) {
+ return $this->hashTrackerCommand->execute(Morph::ProjectContent, $content->id, \array_keys($translateIntoLanguage), $fields);
+ });
+ /** @var TranslateFields $translations */
+ unset($fields);
+
+ foreach ($translations->getFields() as $languageId => $fields) {
+ $params = new \KorElf\TranslateLaravel\DTO\RunTranslateDto();
+ $sourceLanguageCode = null;
+ if (isset($sourceLanguagesCode[$content->language_id]) && $sourceLanguagesCode[$content->language_id] !== 'null') {
+ $sourceLanguageCode = $sourceLanguagesCode[$content->language_id];
+ }
+ foreach ($fields as $field) {
+ $text = $this->noTranslateAttributeHandler->handleAddAttribute((string) $content->{$field} ?? '');
+ $params->addParamHtml($field, $text, $translateIntoLanguage[$languageId], $sourceLanguageCode);
+ }
+
+ $afterTranslateDto = new AfterTranslateDto(ProjectContentServiceCompleted::class, [
+ 'projectId' => $projectId,
+ 'languageId' => $languageId,
+ 'hashes' => $translations->getHashesByLanguage($languageId),
+ 'exceptLanguages' => $exceptLanguages,
+ ]);
+ Translate::runJob($params, $afterTranslateDto);
+ unset($params, $afterTranslateDto);
+ }
+
+ unset($translations, $translateIntoLanguage);
+ }
+ unset($contents, $documentation);
+
+ } catch (\Throwable $e) {
+ \report($e);
+ return $this->errService($e->getMessage());
+ }
+
+ return $this->ok();
+ }
+}
diff --git a/app/application/lang/en/validation.php b/app/application/lang/en/validation.php
index e952264..70cf9a4 100644
--- a/app/application/lang/en/validation.php
+++ b/app/application/lang/en/validation.php
@@ -310,6 +310,7 @@ return [
'content_images' => 'content images',
'source_language_id' => 'source language identifier',
'translate_from_language' => 'translate from language',
+ 'translate-automatically' => 'translate automatically',
'translate-automatically.*' => 'translate automatically',
],
];
diff --git a/app/application/lang/ru/validation.php b/app/application/lang/ru/validation.php
index e8fc506..091718c 100644
--- a/app/application/lang/ru/validation.php
+++ b/app/application/lang/ru/validation.php
@@ -310,6 +310,7 @@ return [
'content_images' => 'изображения контента',
'source_language_id' => 'идентификатор исходного языка',
'translate_from_language' => 'перевести с языка',
+ 'translate-automatically' => 'переводить автоматически',
'translate-automatically.*' => 'переводить автоматически',
],
];
diff --git a/app/application/resources/views/admin/projects/about/_from.blade.php b/app/application/resources/views/admin/projects/about/_from.blade.php
index f3ca3a9..e83216f 100644
--- a/app/application/resources/views/admin/projects/about/_from.blade.php
+++ b/app/application/resources/views/admin/projects/about/_from.blade.php
@@ -1,4 +1,9 @@
@csrf
+@if($serviceTranslationEnable)
+
+@endif