Added the ability to automatically translate simple text.
This commit is contained in:
parent
90aa909b7f
commit
41a7343338
@ -8,10 +8,16 @@ final readonly class Update extends Dto
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private Translations $translations,
|
private Translations $translations,
|
||||||
|
private bool $isTranslateAutomatically = false,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public function getTranslations(): Translations
|
public function getTranslations(): Translations
|
||||||
{
|
{
|
||||||
return $this->translations;
|
return $this->translations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTranslateAutomatically(): bool
|
||||||
|
{
|
||||||
|
return $this->isTranslateAutomatically;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\ProjectTranslationServiceTextHash;
|
||||||
|
|
||||||
|
final class Codes
|
||||||
|
{
|
||||||
|
private array $codes = [];
|
||||||
|
|
||||||
|
public function add(string $code, string $value): self
|
||||||
|
{
|
||||||
|
$this->codes[$code] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCodes(): array
|
||||||
|
{
|
||||||
|
return $this->codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCodeNames(): array
|
||||||
|
{
|
||||||
|
return \array_keys($this->codes);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\ProjectTranslationServiceTextHash;
|
||||||
|
|
||||||
|
final class HashStatusWaiting
|
||||||
|
{
|
||||||
|
private array $hash = [];
|
||||||
|
|
||||||
|
public function add(string $code, bool $isWaiting): void
|
||||||
|
{
|
||||||
|
$this->hash[$code] = $isWaiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isStatusWaiting(string $code): bool
|
||||||
|
{
|
||||||
|
return $this->hash[$code] ?? false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\ProjectTranslationServiceTextHash;
|
||||||
|
|
||||||
|
final class Hashes
|
||||||
|
{
|
||||||
|
private array $hashes = [];
|
||||||
|
private array $ids = [];
|
||||||
|
|
||||||
|
public function add(int $hashId, string $code, string $hash): void
|
||||||
|
{
|
||||||
|
$this->hashes[$hashId] = [
|
||||||
|
'code' => $code,
|
||||||
|
'hash' => $hash,
|
||||||
|
];
|
||||||
|
$this->ids[] = $hashId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHash(int $hashId): ?array
|
||||||
|
{
|
||||||
|
return $this->hashes[$hashId] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIds(): array
|
||||||
|
{
|
||||||
|
return $this->ids;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\ProjectTranslationServiceTextHash;
|
||||||
|
|
||||||
|
final class TranslateCodes
|
||||||
|
{
|
||||||
|
private array $codes = [];
|
||||||
|
private array $hashes = [];
|
||||||
|
|
||||||
|
public function add(int $languageId, string $code, string $hash, int $hashId): self
|
||||||
|
{
|
||||||
|
if (!isset($this->codes[$languageId])) {
|
||||||
|
$this->codes[$languageId] = [];
|
||||||
|
}
|
||||||
|
$this->codes[$languageId][] = $code;
|
||||||
|
|
||||||
|
if (!isset($this->hashes[$languageId])) {
|
||||||
|
$this->hashes[$languageId] = [];
|
||||||
|
}
|
||||||
|
$this->hashes[$languageId][$code] = [
|
||||||
|
'hash' => $hash,
|
||||||
|
'hashId' => $hashId,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCodes(): array
|
||||||
|
{
|
||||||
|
return $this->codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHashesByLanguage(int $languageId): array
|
||||||
|
{
|
||||||
|
return $this->hashes[$languageId] ?? [];
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ final class UpdateRequest extends FormRequest implements FormRequestDto
|
|||||||
'translations' => ['nullable', 'array'],
|
'translations' => ['nullable', 'array'],
|
||||||
'translations.*.code' => ['required', 'string', new In(Translations::getTranslationCodes())],
|
'translations.*.code' => ['required', 'string', new In(Translations::getTranslationCodes())],
|
||||||
'translations.*.text' => ['nullable', 'string', 'max:1000'],
|
'translations.*.text' => ['nullable', 'string', 'max:1000'],
|
||||||
|
'translate-automatically' => ['nullable', 'boolean'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +34,9 @@ final class UpdateRequest extends FormRequest implements FormRequestDto
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Update($translations);
|
return new Update(
|
||||||
|
translations: $translations,
|
||||||
|
isTranslateAutomatically: (bool) $this->input('translate-automatically', false),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Jobs\Translate;
|
||||||
|
|
||||||
|
use App\Services\Translate\Project\TranslationTextService;
|
||||||
|
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 ProcessTranslationText 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 $projectId,
|
||||||
|
private readonly int $languageId,
|
||||||
|
private readonly array $translateTextCode = [],
|
||||||
|
private readonly array $exceptLanguages = [],
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique ID for the job.
|
||||||
|
*/
|
||||||
|
public function uniqueId(): string
|
||||||
|
{
|
||||||
|
return 'ProcessTranslationText-' . $this->projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function handle(TranslationTextService $translationTextService): void
|
||||||
|
{
|
||||||
|
$result = $translationTextService->translate($this->projectId, $this->languageId, $this->translateTextCode, $this->exceptLanguages);
|
||||||
|
if ($result->isError() && $result->getCode() !== 404) {
|
||||||
|
cache()->lock($this->uniqueId())->forceRelease();
|
||||||
|
throw new Exception($result->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\ProjectTranslationServiceHashes\Status;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
final class ProjectTranslationServiceTextHash extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'project_translation_service_text_hashes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The model's default values for attributes.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $attributes = [
|
||||||
|
'status' => Status::Waiting,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'language_id',
|
||||||
|
'code',
|
||||||
|
'status',
|
||||||
|
'hash',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'status' => Status::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -22,4 +22,13 @@ final readonly class ProjectTranslationRepository
|
|||||||
$query = ProjectTranslation::query()->withTrashed()->where('project_id', $projectId)->where('language_id', $languageId);
|
$query = ProjectTranslation::query()->withTrashed()->where('project_id', $projectId)->where('language_id', $languageId);
|
||||||
return $this->createSearchInstanceCommand->execute($query);
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getProjectTranslationByCodes(int $projectId, int $languageId, array $codes): Search
|
||||||
|
{
|
||||||
|
$query = ProjectTranslation::query()
|
||||||
|
->where('project_id', $projectId)
|
||||||
|
->where('language_id', $languageId)
|
||||||
|
->whereIn('code', $codes);
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Contracts\Search;
|
||||||
|
use App\Models\ProjectTranslationServiceTextHash;
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
final readonly class ProjectTranslationServiceTextHashRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getHashes(array $codes, ?array $languages = null): Search
|
||||||
|
{
|
||||||
|
$query = ProjectTranslationServiceTextHash::query()
|
||||||
|
->whereIn('code', $codes)
|
||||||
|
->when($languages, function (Builder $query) use ($languages) {
|
||||||
|
$query->whereIn('language_id', $languages);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHashesByIds(array $ids): Search
|
||||||
|
{
|
||||||
|
$query = ProjectTranslationServiceTextHash::query()->whereIn('id', $ids);
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,10 @@
|
|||||||
namespace App\Services\Admin\Project;
|
namespace App\Services\Admin\Project;
|
||||||
|
|
||||||
use App\Dto\Service\Admin\Project\Translation\Translations;
|
use App\Dto\Service\Admin\Project\Translation\Translations;
|
||||||
|
use App\Dto\Service\Admin\Project\Translation\Translation;
|
||||||
use App\Dto\Service\Admin\Project\Translation\Update;
|
use App\Dto\Service\Admin\Project\Translation\Update;
|
||||||
use App\Enums\CacheTag;
|
use App\Enums\CacheTag;
|
||||||
|
use App\Jobs\Translate\ProcessTranslationText;
|
||||||
use App\Models\ProjectTranslation;
|
use App\Models\ProjectTranslation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Repositories\ProjectRepository;
|
use App\Repositories\ProjectRepository;
|
||||||
@ -60,6 +62,7 @@ final class TranslationService extends Service
|
|||||||
'language' => $language,
|
'language' => $language,
|
||||||
'projectTranslations' => $this->projectTranslationRepository->getProjectTranslations($projectId, $languageId)->all()->pluck('text', 'code')->toArray(),
|
'projectTranslations' => $this->projectTranslationRepository->getProjectTranslations($projectId, $languageId)->all()->pluck('text', 'code')->toArray(),
|
||||||
'translations' => Translations::getTranslationCodes(),
|
'translations' => Translations::getTranslationCodes(),
|
||||||
|
'serviceTranslationEnable' => config('translation_service.enable', false),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +84,9 @@ final class TranslationService extends Service
|
|||||||
$this->translationModelSyncCommand->execute($project, $language, $data->getTranslations());
|
$this->translationModelSyncCommand->execute($project, $language, $data->getTranslations());
|
||||||
});
|
});
|
||||||
$this->clearCacheCommandHandler->byTag(CacheTag::ProjectTranslation);
|
$this->clearCacheCommandHandler->byTag(CacheTag::ProjectTranslation);
|
||||||
|
if (\config('translation_service.enable', false)) {
|
||||||
|
$this->translateContent($projectId, $languageId, $data);
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
report($e);
|
report($e);
|
||||||
return $this->errService(__('Server Error'));
|
return $this->errService(__('Server Error'));
|
||||||
@ -88,4 +94,19 @@ final class TranslationService extends Service
|
|||||||
|
|
||||||
return $this->ok(__('Translations successfully updated'));
|
return $this->ok(__('Translations successfully updated'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function translateContent(int $projectId, int $languageId, Update $data): void
|
||||||
|
{
|
||||||
|
if (! $data->isTranslateAutomatically()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$translateExceptLanguages = [$languageId];
|
||||||
|
$translateTextCode = [];
|
||||||
|
foreach ($data->getTranslations()->getTranslations() as $translation) {
|
||||||
|
/** @var Translation $translation */
|
||||||
|
$translateTextCode[] = $translation->getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessTranslationText::dispatch($projectId, $languageId, $translateTextCode, $translateExceptLanguages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\ProjectTranslation;
|
||||||
|
|
||||||
|
use App\Models\ProjectLanguage;
|
||||||
|
|
||||||
|
final readonly class TranslationText
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ProjectLanguage $language,
|
||||||
|
private array $translations,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public static function init(ProjectLanguage $language, array $translations): self
|
||||||
|
{
|
||||||
|
return new self($language, $translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function translate(string $code): string
|
||||||
|
{
|
||||||
|
return $this->translations[$code] ?? __($code, [], $this->language->system_lang?->getLocale());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\ProjectTranslation;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\ProjectLanguage;
|
||||||
|
use App\Repositories\ProjectTranslationRepository;
|
||||||
|
|
||||||
|
final readonly class TranslationTextCommand
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ProjectTranslationRepository $translationRepository,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function execute(Project $project, ProjectLanguage $language, array $codes): TranslationText
|
||||||
|
{
|
||||||
|
$translations = $this->translationRepository
|
||||||
|
->getProjectTranslationByCodes($project->id, $language->id, $codes)
|
||||||
|
->all()->pluck('text', 'code');
|
||||||
|
|
||||||
|
return TranslationText::init($language, $translations->toArray());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\ProjectTranslationServiceTextHash;
|
||||||
|
|
||||||
|
use App\Dto\Service\ProjectTranslationServiceTextHash\Hashes;
|
||||||
|
use App\Dto\Service\ProjectTranslationServiceTextHash\HashStatusWaiting;
|
||||||
|
use App\Enums\ProjectTranslationServiceHashes\Status;
|
||||||
|
use App\Repositories\ProjectTranslationServiceTextHashRepository;
|
||||||
|
|
||||||
|
final readonly class CompletionChecker
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ProjectTranslationServiceTextHashRepository $hashRepository,
|
||||||
|
private ProjectTranslationServiceTextHashCommandHandler $commandHandler,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function execute(Hashes $hashesDto): HashStatusWaiting
|
||||||
|
{
|
||||||
|
$hashes = $this->hashRepository->getHashesByIds($hashesDto->getIds())->all();
|
||||||
|
$hashStatusWaiting = new HashStatusWaiting();
|
||||||
|
$hashSuccessIds = [];
|
||||||
|
foreach ($hashes as $hash) {
|
||||||
|
$dataHash = $hashesDto->getHash($hash->id);
|
||||||
|
if ($dataHash === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isWaiting = false;
|
||||||
|
if ($hash->status === Status::Waiting && $hash->hash === $dataHash['hash'] && $hash->code === $dataHash['code']) {
|
||||||
|
$isWaiting = true;
|
||||||
|
$hashSuccessIds[] = $hash->id;
|
||||||
|
}
|
||||||
|
$hashStatusWaiting->add($hash->code, $isWaiting);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->commandHandler->handleSetStatusById($hashSuccessIds, Status::Success);
|
||||||
|
|
||||||
|
return $hashStatusWaiting;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\ProjectTranslationServiceTextHash;
|
||||||
|
|
||||||
|
use App\Dto\Service\ProjectTranslationServiceTextHash\Codes;
|
||||||
|
use App\Dto\Service\ProjectTranslationServiceTextHash\TranslateCodes;
|
||||||
|
use App\Enums\ProjectTranslationServiceHashes\Status;
|
||||||
|
use App\Models\ProjectTranslationServiceTextHash;
|
||||||
|
use App\Repositories\ProjectTranslationServiceTextHashRepository;
|
||||||
|
|
||||||
|
final readonly class HashTrackerCommand
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ProjectTranslationServiceTextHashRepository $hashRepository,
|
||||||
|
private ProjectTranslationServiceTextHashCommandHandler $commandHandler,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function execute(array $languages, Codes $codes): TranslateCodes
|
||||||
|
{
|
||||||
|
$hashes = $this->hashRepository->getHashes($codes->getCodeNames(), $languages)->all();
|
||||||
|
|
||||||
|
$translateCodes = new TranslateCodes();
|
||||||
|
foreach ($codes->getCodes() as $code => $value) {
|
||||||
|
$textHash = $this->generateHashFromText((string) $value);
|
||||||
|
foreach ($languages as $language) {
|
||||||
|
$modelHash = $hashes->where('language_id', $language)->firstWhere('code', $code);
|
||||||
|
if ($modelHash === null) {
|
||||||
|
$modelHash = $this->commandHandler->handleStore([
|
||||||
|
'language_id' => $language,
|
||||||
|
'code' => $code,
|
||||||
|
'hash' => $textHash,
|
||||||
|
'status' => Status::Waiting,
|
||||||
|
]);
|
||||||
|
$translateCodes->add($language, $code, $textHash, $modelHash->id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($modelHash->hash === $textHash && $this->isSuccess($modelHash)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translateCodes->add($language, $code, $textHash, $modelHash->id);
|
||||||
|
$this->commandHandler->handleUpdate($modelHash, [
|
||||||
|
'hash' => $textHash,
|
||||||
|
'status' => Status::Waiting,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $translateCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateHashFromText(string $text): string
|
||||||
|
{
|
||||||
|
return \hash('sha256', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isSuccess(ProjectTranslationServiceTextHash $modelHash): bool
|
||||||
|
{
|
||||||
|
if ($modelHash->status === Status::Success) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($modelHash->updated_at >= now()->subMinutes(10));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\ProjectTranslationServiceTextHash;
|
||||||
|
|
||||||
|
use App\Enums\ProjectTranslationServiceHashes\Status;
|
||||||
|
use App\Models\ProjectTranslationServiceTextHash;
|
||||||
|
|
||||||
|
final readonly class ProjectTranslationServiceTextHashCommandHandler
|
||||||
|
{
|
||||||
|
public function handleStore(array $data): ProjectTranslationServiceTextHash
|
||||||
|
{
|
||||||
|
return ProjectTranslationServiceTextHash::create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUpdate(ProjectTranslationServiceTextHash $hash, array $data): ProjectTranslationServiceTextHash
|
||||||
|
{
|
||||||
|
$hash->update($data);
|
||||||
|
$hash->touch();
|
||||||
|
return $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleSetStatusById(array $ids, Status $status): void
|
||||||
|
{
|
||||||
|
ProjectTranslationServiceTextHash::query()->whereIn('id', $ids)->update(['status' => $status->value]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Translate\Completed\Project;
|
||||||
|
|
||||||
|
use App\Dto\Service\ProjectTranslationServiceTextHash\Hashes;
|
||||||
|
use App\Exceptions\Services\Translate\CompletedException;
|
||||||
|
use App\Jobs\Translate\ProcessTranslationText;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\ProjectTranslation;
|
||||||
|
use App\Repositories\ProjectRepository;
|
||||||
|
use App\Repositories\ProjectTranslationRepository;
|
||||||
|
use App\Services\ProjectTranslationServiceTextHash\CompletionChecker;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener;
|
||||||
|
|
||||||
|
final readonly class TranslationTextService implements TranslationCompletedListener
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ProjectRepository $projectRepository,
|
||||||
|
private CompletionChecker $completionChecker,
|
||||||
|
private ProjectTranslationRepository $projectTranslationRepository,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws CompletedException
|
||||||
|
*/
|
||||||
|
public function onTranslationCompleted(array $translatedText, array $data = []): void
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
!isset($data['projectId'])
|
||||||
|
|| !isset($data['languageId'])
|
||||||
|
|| !isset($data['hashes'])
|
||||||
|
) {
|
||||||
|
throw new CompletedException('Required data is missing: projectId, languageId, or hashes.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = $this->projectRepository->getProjectById((int) $data['projectId']);
|
||||||
|
if ($project === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations = $this->projectTranslationRepository->getProjectTranslationByCodes($project->id, $data['languageId'], \array_keys($translatedText))->all();
|
||||||
|
|
||||||
|
$translateTextCode = DB::transaction(function () use ($data, $translatedText, $translations) {
|
||||||
|
$translateTextCode = [];
|
||||||
|
$hashes = $this->completionChecker->execute(
|
||||||
|
$this->getHashes($data['hashes']),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($translatedText as $translatedTextKey => $translatedTextValue) {
|
||||||
|
if ($hashes->isStatusWaiting($translatedTextKey) !== true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$translateTextCode[] = $translatedTextKey;
|
||||||
|
$translation = $translations->firstWhere('code', $translatedTextKey);
|
||||||
|
if ($translation !== null) {
|
||||||
|
$translation->update([
|
||||||
|
'text' => $translatedTextValue,
|
||||||
|
]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectTranslation::create([
|
||||||
|
'project_id' => (int) $data['projectId'],
|
||||||
|
'language_id' => (int) $data['languageId'],
|
||||||
|
'code' => $translatedTextKey,
|
||||||
|
'text' => $translatedTextValue,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $translateTextCode;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (\config('translation_service.enable', false) && !empty($translateTextCode)) {
|
||||||
|
$this->translateContent($project, $translateTextCode, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function translateContent(Project $project, array $translateTextCode, array $data): void
|
||||||
|
{
|
||||||
|
$translateExceptLanguages = $data['exceptLanguages'] ?? [];
|
||||||
|
$translateExceptLanguages[] = $data['languageId'];
|
||||||
|
ProcessTranslationText::dispatch($project->id, $data['languageId'], $translateTextCode, $translateExceptLanguages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getHashes(array $hashes): Hashes
|
||||||
|
{
|
||||||
|
$hashesDto = new Hashes();
|
||||||
|
foreach ($hashes as $code => $hash) {
|
||||||
|
$hashesDto->add((int) $hash['hashId'], $code, $hash['hash']);
|
||||||
|
}
|
||||||
|
return $hashesDto;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Translate\Project;
|
||||||
|
|
||||||
|
use App\Dto\Service\ProjectTranslationServiceTextHash\Codes;
|
||||||
|
use App\Dto\Service\ProjectTranslationServiceTextHash\TranslateCodes;
|
||||||
|
use App\Repositories\ProjectRepository;
|
||||||
|
use App\Repositories\ProjectTranslationServiceRepository;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\Services\ProjectTranslation\TranslationText;
|
||||||
|
use App\Services\ProjectTranslation\TranslationTextCommand;
|
||||||
|
use App\Services\ProjectTranslationServiceTextHash\HashTrackerCommand;
|
||||||
|
use App\Services\Translate\Completed\Project\TranslationTextService as TranslationTextServiceCompleted;
|
||||||
|
use App\Services\Service;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use KorElf\TranslateLaravel\DTO\AfterTranslateDto;
|
||||||
|
use KorElf\TranslateLaravel\Facades\Translate;
|
||||||
|
|
||||||
|
final class TranslationTextService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProjectRepository $projectRepository,
|
||||||
|
private readonly TranslationTextCommand $translationTextCommand,
|
||||||
|
private readonly ProjectTranslationServiceRepository $projectTranslationServiceRepository,
|
||||||
|
private readonly HashTrackerCommand $hashTrackerCommand,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function translate(int $projectId, int $languageId, array $translateTextCode, array $exceptLanguages): ServiceResultSuccess | ServiceResultError
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$project = $this->projectRepository->getProjectById($projectId);
|
||||||
|
if ($project === null) {
|
||||||
|
return $this->errNotFound(__('Project not found'));
|
||||||
|
}
|
||||||
|
$language = $project->languages()->firstWhere('id', $languageId);
|
||||||
|
if ($language === null) {
|
||||||
|
return $this->errNotFound(__('Language not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$translateIntoLanguage = $this->projectTranslationServiceRepository
|
||||||
|
->getLanguagesBySourceLanguage($languageId, $exceptLanguages)
|
||||||
|
->all()->pluck('code', 'language_id')->toArray();
|
||||||
|
|
||||||
|
if (empty($translateIntoLanguage)) {
|
||||||
|
return $this->ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
$translationText = $this->translationTextCommand->execute($project, $language, $translateTextCode);
|
||||||
|
$codes = $this->getCodes($translationText, $translateTextCode);
|
||||||
|
$translations = DB::transaction(function () use ($translateIntoLanguage, $codes) {
|
||||||
|
return $this->hashTrackerCommand->execute(\array_keys($translateIntoLanguage), $codes);
|
||||||
|
});
|
||||||
|
/** @var TranslateCodes $translations */
|
||||||
|
unset($codes);
|
||||||
|
|
||||||
|
$sourceLanguageCode = $this->projectTranslationServiceRepository->getLanguageCodeByLanguageId($language->id);
|
||||||
|
foreach ($translations->getCodes() as $currentLanguageId => $codes) {
|
||||||
|
$params = new \KorElf\TranslateLaravel\DTO\RunTranslateDto();
|
||||||
|
foreach ($codes as $code) {
|
||||||
|
$text = $translationText->translate($code);
|
||||||
|
$params->addParamHtml($code, $text, $translateIntoLanguage[$currentLanguageId], $sourceLanguageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$afterTranslateDto = new AfterTranslateDto(TranslationTextServiceCompleted::class, [
|
||||||
|
'projectId' => $projectId,
|
||||||
|
'languageId' => $currentLanguageId,
|
||||||
|
'hashes' => $translations->getHashesByLanguage($currentLanguageId),
|
||||||
|
'exceptLanguages' => $exceptLanguages,
|
||||||
|
]);
|
||||||
|
Translate::runJob($params, $afterTranslateDto);
|
||||||
|
unset($params, $afterTranslateDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($translations, $translateIntoLanguage, $project, $language, $translationText, $sourceLanguageCode);
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\report($e);
|
||||||
|
return $this->errService($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCodes(TranslationText $translationText, array $translateTextCode): Codes
|
||||||
|
{
|
||||||
|
$codes = new Codes();
|
||||||
|
foreach ($translateTextCode as $code) {
|
||||||
|
$codes->add($code, $translationText->translate($code));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $codes;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?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_translation_service_text_hashes', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('code');
|
||||||
|
$table->unsignedBigInteger('language_id')->index();
|
||||||
|
$table->foreign('language_id')->references('id')->on('project_languages');
|
||||||
|
$table->unsignedInteger('status')->index()->default(0);
|
||||||
|
$table->char('hash', 64);
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['language_id', 'code'], 'unique_language_code');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('project_translation_service_text_hashes');
|
||||||
|
}
|
||||||
|
};
|
@ -8,6 +8,11 @@
|
|||||||
<h3 id="category" class="mb-4">{{ __('admin-sections.Translations') }}</h3>
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Translations') }}</h3>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<form method="post" action="{{ route('admin.projects.translations.update', ['project' => $project->id, 'language' => $language->id]) }}">
|
<form method="post" action="{{ route('admin.projects.translations.update', ['project' => $project->id, 'language' => $language->id]) }}">
|
||||||
|
@if($serviceTranslationEnable)
|
||||||
|
<x-volt.forms.checkbox :title="__('Automatic translation')" name="translate-automatically"
|
||||||
|
:user-value="1" class="language-content" checkbox-value="1"
|
||||||
|
not-checked-value="0"/><br>
|
||||||
|
@endif
|
||||||
<table class="table table-centered table-nowrap mb-0 rounded">
|
<table class="table table-centered table-nowrap mb-0 rounded">
|
||||||
<thead class="thead-light">
|
<thead class="thead-light">
|
||||||
<tr>
|
<tr>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user