Версия 0.4.0 #7
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -0,0 +1,58 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs\Translate;
|
||||
|
||||
use App\Services\Translate\Project\DocumentationCategoryContentService;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
|
||||
final class ProcessProjectDocumentationCategoryContent implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, Queueable;
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Translate\Completed\Project;
|
||||
|
||||
use App\Dto\Service\ProjectTranslationServiceHash\Hashes;
|
||||
use App\Exceptions\Services\Translate\CompletedException;
|
||||
use App\Jobs\Translate\ProcessProjectDocumentationCategoryContent;
|
||||
use App\Models\DocumentationCategory;
|
||||
use App\Models\DocumentationCategoryContent;
|
||||
use App\Repositories\DocumentationCategoryRepository;
|
||||
use App\Services\ProjectTranslationService\NoTranslateAttributeHandler;
|
||||
use App\Services\ProjectTranslationServiceHash\CompletionChecker;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener;
|
||||
|
||||
final class DocumentationCategoryContentService implements TranslationCompletedListener
|
||||
{
|
||||
public function __construct(
|
||||
private DocumentationCategoryRepository $categoryRepository,
|
||||
private NoTranslateAttributeHandler $noTranslateAttributeHandler,
|
||||
private CompletionChecker $completionChecker,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @throws CompletedException
|
||||
*/
|
||||
public function onTranslationCompleted(array $translatedText, array $data = []): void
|
||||
{
|
||||
if (
|
||||
!isset($data['categoryId'])
|
||||
|| !isset($data['languageId'])
|
||||
|| !isset($data['hashes'])
|
||||
) {
|
||||
throw new CompletedException('Required data is missing: categoryId, languageId, or hashes.');
|
||||
}
|
||||
|
||||
$category = $this->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;
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Translate\Project;
|
||||
|
||||
use App\Dto\Service\ProjectTranslationServiceHash\Fields;
|
||||
use App\Dto\Service\ProjectTranslationServiceHash\TranslateFields;
|
||||
use App\Enums\Morph;
|
||||
use App\Repositories\DocumentationCategoryRepository;
|
||||
use App\Repositories\ProjectTranslationServiceRepository;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\Services\ProjectTranslationService\NoTranslateAttributeHandler;
|
||||
use App\Services\ProjectTranslationServiceHash\HashTrackerCommand;
|
||||
use App\Services\Translate\Completed\Project\DocumentationCategoryContentService as DocumentationCategoryContentServiceCompleted;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use KorElf\TranslateLaravel\DTO\AfterTranslateDto;
|
||||
use KorElf\TranslateLaravel\Facades\Translate;
|
||||
|
||||
final class DocumentationCategoryContentService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DocumentationCategoryRepository $documentationCategoryRepository,
|
||||
private readonly ProjectTranslationServiceRepository $projectTranslationServiceRepository,
|
||||
private readonly HashTrackerCommand $hashTrackerCommand,
|
||||
private readonly NoTranslateAttributeHandler $noTranslateAttributeHandler,
|
||||
) {}
|
||||
|
||||
public function translate(int $categoryId, array $contentIds, array $exceptLanguages): ServiceResultSuccess | ServiceResultError
|
||||
{
|
||||
try {
|
||||
$category = $this->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();
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
@ -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": "Категория не найдена"
|
||||
}
|
||||
|
@ -20,6 +20,11 @@
|
||||
@endphp
|
||||
<div class="tab-pane fade @if($index === 0) show active @endif" id="language-{{ $language->id }}" role="tabpanel" aria-labelledby="language-{{ $language->id }}-tab">
|
||||
<x-volt.forms.checkbox :title="__('Edit')" :name="'content-enable-' . $language->id" :user-value="($index === 0) ? 1 : 0" class="content-enable" checkbox-value="1" notCheckedValue="0"/>
|
||||
@if($serviceTranslationEnable)
|
||||
<x-volt.forms.checkbox :title="__('Automatic translation')" :name="'translate-automatically[' . $language->id . ']'"
|
||||
:user-value="1" class="language-content" checkbox-value="1"
|
||||
not-checked-value="0"/>
|
||||
@endif
|
||||
<x-volt.forms.input :title="__('validation.attributes.title')" :name="'content[' . $language->id . '][title]'" type="text" class="language-content" :disabled="$index !== 0" :value="$content?->title ?? ''" required />
|
||||
</div>
|
||||
@endforeach
|
||||
|
Loading…
x
Reference in New Issue
Block a user