diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Content.php b/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Content.php index 91b2763..5190c8e 100644 --- a/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Content.php +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Content.php @@ -7,6 +7,7 @@ final readonly class Content public function __construct( private int $languageId, private string $title, + private bool $isTranslateAutomatically = false, ) { } public function getLanguageId(): int @@ -18,4 +19,9 @@ final readonly class Content { return $this->title; } + + public function isTranslateAutomatically(): bool + { + return $this->isTranslateAutomatically; + } } diff --git a/app/application/app/Enums/Morph.php b/app/application/app/Enums/Morph.php index 8d1214a..50c4583 100644 --- a/app/application/app/Enums/Morph.php +++ b/app/application/app/Enums/Morph.php @@ -2,6 +2,7 @@ namespace App\Enums; +use App\Models\DocumentationCategoryContent; use App\Models\DocumentationContent; use App\Models\Project; use App\Models\ProjectContent; @@ -11,13 +12,15 @@ enum Morph: int case Project = 1; case DocumentationContent = 2; case ProjectContent = 3; + case DocumentationCategoryContent = 4; public function getPathModel(): string { return match ($this) { - self::Project => Project::class, - self::DocumentationContent => DocumentationContent::class, - self::ProjectContent => ProjectContent::class, + self::Project => Project::class, + self::DocumentationContent => DocumentationContent::class, + self::ProjectContent => ProjectContent::class, + self::DocumentationCategoryContent => DocumentationCategoryContent::class, }; } diff --git a/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/StoreUpdateRequest.php index a3560c7..32c7c14 100644 --- a/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/StoreUpdateRequest.php +++ b/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/StoreUpdateRequest.php @@ -21,6 +21,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto 'sort' => ['required', 'integer', 'min:-1000', 'max:1000'], 'parent_id' => ['nullable', 'integer', 'exists:documentation_categories,id'], 'content.*.title' => ['required', 'string', 'max:255'], + 'translate-automatically.*' => ['nullable', 'boolean'], ]; } @@ -47,6 +48,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto $contents->addContent(new Content( languageId: (int) $languageId, title: $content['title'], + isTranslateAutomatically: (bool) $this->input('translate-automatically.' . $languageId, false), )); } return $contents; diff --git a/app/application/app/Jobs/Translate/ProcessProjectDocumentationCategoryContent.php b/app/application/app/Jobs/Translate/ProcessProjectDocumentationCategoryContent.php new file mode 100644 index 0000000..952f3d0 --- /dev/null +++ b/app/application/app/Jobs/Translate/ProcessProjectDocumentationCategoryContent.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 $projectDocumentCategoryId, + private readonly array $contentIds = [], + private readonly array $exceptLanguages = [], + ) { } + + /** + * Get the unique ID for the job. + */ + public function uniqueId(): string + { + return 'documentation-category-' . $this->projectDocumentCategoryId; + } + + /** + * Execute the job. + * @throws Exception + */ + public function handle(DocumentationCategoryContentService $categoryContentService): void + { + $result = $categoryContentService->translate($this->projectDocumentCategoryId, $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/Services/Admin/Project/DocumentationCategoryService.php b/app/application/app/Services/Admin/Project/DocumentationCategoryService.php index b1390b6..7abce3d 100644 --- a/app/application/app/Services/Admin/Project/DocumentationCategoryService.php +++ b/app/application/app/Services/Admin/Project/DocumentationCategoryService.php @@ -5,8 +5,11 @@ namespace App\Services\Admin\Project; use App\Dto\QuerySettingsDto; use App\Dto\Builder\DocumentationCategory as DocumentationCategoryBuilderDto; use App\Dto\Service\Admin\Project\DocumentationCategory\StoreUpdate; +use App\Dto\Service\Admin\Project\DocumentationCategoryContent\Content; +use App\Dto\Service\Admin\Project\DocumentationCategoryContent\Contents; use App\Exceptions\Services\DocumentationCategory\ParentException; use App\Exceptions\Services\ServiceException; +use App\Jobs\Translate\ProcessProjectDocumentationCategoryContent; use App\Models\DocumentationCategory; use App\Models\DocumentationCategoryContent; use App\Models\ProjectLanguage; @@ -90,6 +93,7 @@ final class DocumentationCategoryService extends Service 'project' => $project, 'category' => new DocumentationCategory(), 'categories' => $this->documentationCategoryRepository->getForSelect($version, $defaultLanguage), + 'serviceTranslationEnable' => config('translation_service.enable', false), ]); } @@ -120,6 +124,7 @@ final class DocumentationCategoryService extends Service 'project' => $project, 'category' => $category, 'categories' => $this->documentationCategoryRepository->getForSelect($version, $defaultLanguage, $category, $withCategories), + 'serviceTranslationEnable' => config('translation_service.enable', false), ]); } @@ -151,6 +156,10 @@ final class DocumentationCategoryService extends Service return $category; }); + + if (\config('translation_service.enable', false)) { + $this->translateContent($category, $data->getContents()); + } } catch (ServiceException $e) { return $e->getServiceResultError(); } catch (\Throwable $e) { @@ -201,6 +210,10 @@ final class DocumentationCategoryService extends Service return $category; }); + + if (\config('translation_service.enable', false)) { + $this->translateContent($category, $data->getContents()); + } } catch (ServiceException $e) { return $e->getServiceResultError(); } catch (ParentException $e) { @@ -254,4 +267,27 @@ final class DocumentationCategoryService extends Service 'parent_id' => $data->getParentId(), ]; } + + private function translateContent(DocumentationCategory $category, Contents $contents): void + { + $translateExceptLanguages = []; + + $translateLanguages = []; + foreach ($contents->getContents() as $content) { + /** @var Content $content */ + $translateExceptLanguages[] = $content->getLanguageId(); + if ($content->isTranslateAutomatically()) { + $translateLanguages[] = $content->getLanguageId(); + } + } + + if (empty($translateLanguages)) { + return; + } + + $translateContentIds = $category->contents()->select('id') + ->whereIn('language_id', $translateLanguages) + ->get()->pluck('id')->toArray(); + ProcessProjectDocumentationCategoryContent::dispatch($category->id, $translateContentIds, $translateExceptLanguages); + } } diff --git a/app/application/app/Services/Translate/Completed/Project/DocumentationCategoryContentService.php b/app/application/app/Services/Translate/Completed/Project/DocumentationCategoryContentService.php new file mode 100644 index 0000000..d521ae6 --- /dev/null +++ b/app/application/app/Services/Translate/Completed/Project/DocumentationCategoryContentService.php @@ -0,0 +1,89 @@ +categoryRepository->getCategoryById((int) $data['categoryId']); + if ($category === null) { + return; + } + + $categoryContent = DB::transaction(function () use ($data, $category, $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; + } + + $categoryContent = $category->contents()->where('language_id', $data['languageId'])->first(); + if ($categoryContent !== null) { + $categoryContent->update($values); + return $categoryContent; + } + + $values['language_id'] = $data['languageId']; + return $category->contents()->create($values); + }); + if (\config('translation_service.enable', false) && $categoryContent !== null) { + $this->translateContent($category, $categoryContent, $data); + } + } + + private function translateContent(DocumentationCategory $category, DocumentationCategoryContent $categoryContent, array $data): void + { + $translateExceptLanguages = $data['exceptLanguages'] ?? []; + $translateExceptLanguages[] = $data['languageId']; + ProcessProjectDocumentationCategoryContent::dispatch($category->id, [$categoryContent->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/DocumentationCategoryContentService.php b/app/application/app/Services/Translate/Project/DocumentationCategoryContentService.php new file mode 100644 index 0000000..73b7baa --- /dev/null +++ b/app/application/app/Services/Translate/Project/DocumentationCategoryContentService.php @@ -0,0 +1,94 @@ +documentationCategoryRepository->getCategoryById($categoryId); + if ($category === null) { + return $this->errNotFound(__('Category not found')); + } + + $contents = $category->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 ?? ''); + $translations = DB::transaction(function () use ($content, $translateIntoLanguage, $fields) { + return $this->hashTrackerCommand->execute(Morph::DocumentationCategoryContent, $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(DocumentationCategoryContentServiceCompleted::class, [ + 'categoryId' => $categoryId, + '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.json b/app/application/lang/en.json index 56d5005..d976cca 100644 --- a/app/application/lang/en.json +++ b/app/application/lang/en.json @@ -271,5 +271,6 @@ "Category successfully deleted": "Category successfully deleted", "Old Files deleted": "Old Files deleted", "Automatic translation": "Automatic translation", - "The settings were saved successfully": "The settings were saved successfully" + "The settings were saved successfully": "The settings were saved successfully", + "Category not found": "Category not found" } diff --git a/app/application/lang/ru.json b/app/application/lang/ru.json index 9e8bcf7..0d55c4b 100644 --- a/app/application/lang/ru.json +++ b/app/application/lang/ru.json @@ -271,5 +271,6 @@ "Category successfully deleted": "Категория успешно удалена", "Old Files deleted": "Старые файлы удалены", "Automatic translation": "Автоматический перевод", - "The settings were saved successfully": "Настройки успешно сохранены" + "The settings were saved successfully": "Настройки успешно сохранены", + "Category not found": "Категория не найдена" } diff --git a/app/application/resources/views/admin/projects/documentation-categories/_from.blade.php b/app/application/resources/views/admin/projects/documentation-categories/_from.blade.php index f83b9b0..1bd9aa7 100644 --- a/app/application/resources/views/admin/projects/documentation-categories/_from.blade.php +++ b/app/application/resources/views/admin/projects/documentation-categories/_from.blade.php @@ -20,6 +20,11 @@ @endphp
+ @if($serviceTranslationEnable) + + @endif
@endforeach