13 Commits

Author SHA1 Message Date
c112b225dc Update byTag method to specify void return type
Added an explicit void return type to the byTag method for clarity and strict type enforcement.
2025-02-14 23:33:39 +05:00
e3194cef48 Update MDHub image version to 0.4.0 in production setup 2025-02-14 21:52:17 +05:00
41a7343338 Added the ability to automatically translate simple text. 2025-02-13 22:43:39 +05:00
90aa909b7f Fix incorrect property access in CompletionChecker
Corrected the property access from array syntax to object syntax for `$hash->field` in the `CompletionChecker.php` file. This ensures proper handling of fields and prevents potential runtime errors.
2025-02-13 22:42:40 +05:00
8b35a5691f Refactor language filter to use conditional query builder
Removed redundant `whereIn` clause to simplify the query logic.
2025-02-12 21:38:52 +05:00
844264fb62 Added automatic translation about the project. 2025-02-09 21:39:18 +05:00
d8d17064d0 Mark DocumentationCategoryContentService as readonly. 2025-02-09 20:55:40 +05:00
4dc6060fb3 Added support for automatic translation of documentation categories. 2025-02-09 20:00:34 +05:00
58a256e1e4 Added the ability to automatically translate documentation content. 2025-02-09 18:58:51 +05:00
25faa3d62b Added settings for translation into other languages ​​in the project. 2025-02-09 18:53:08 +05:00
c4817a675a Standardize Dockerfile stage naming convention
Updated all Dockerfile stage names to adhere to a consistent lowercase naming convention with underscores. This improves readability and aligns with best practices for naming Docker build stages.
2025-02-09 18:26:05 +05:00
cb2161356e Remove commented-out alternate image references in configs
Cleaned up unused image lines referencing MDHub in multiple docker-compose files.
2025-02-09 18:25:09 +05:00
b729d057a9 Add writable .composer directory for Composer cache.
This commit ensures a writable .composer directory is created with appropriate permissions. This change resolves potential permission issues when using Composer inside the container.
2024-12-10 22:22:50 +05:00
93 changed files with 4773 additions and 833 deletions

View File

@@ -7,6 +7,7 @@
**/storage/framework/sessions/*
**/storage/framework/views/*
**/storage/framework/testing/*
**/storage/translation_service/*
**/storage/logs/*
**/vendor/
**/node_modules/

View File

@@ -15,6 +15,19 @@ CAPTCHA_PUBLIC_TOKEN=
FEEDBACK_MAIL_NOTIFICATIONS=false
FEEDBACK_MAIL_TO=
TRANSLATION_SERVICE_ENABLE=false
# yandex or log
TRANSLATE_SERVICE=log
TRANSLATE_YANDEX_FOLDER_ID=
TRANSLATE_YANDEX_AUTHORIZED_KEY_PATH=/storage/translation_service/authorized_key.json
TRANSLATE_YANDEX_LIMIT_MAX_REQUEST=20
TRANSLATE_YANDEX_LIMIT_RATE_SECONDS=1
TRANSLATE_YANDEX_LIMIT_MAX_SYMBOLS=9000
TRANSLATE_LOG_LIMIT_MAX_REQUEST=20
TRANSLATE_LOG_LIMIT_RATE_SECONDS=1
TRANSLATE_LOG_LIMIT_MAX_SYMBOLS=9000
APP_FORCE_HTTPS=false
APP_DEFAULT_LOCALE=ru

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -11,6 +11,7 @@ final readonly class Content
private string $title,
private string $content,
private Storages $storages,
private bool $isTranslateAutomatically = false,
) { }
public function getLanguageId(): int
@@ -32,4 +33,9 @@ final readonly class Content
{
return $this->storages;
}
public function isTranslateAutomatically(): bool
{
return $this->isTranslateAutomatically;
}
}

View File

@@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\Admin\Project\ServiceTranslate;
use App\Dto\Service\Dto;
final readonly class Translation extends Dto
{
public function __construct(
private int $languageId,
private ?int $sourceLanguageId,
private ?string $code,
) { }
public function getLanguageId(): int
{
return $this->languageId;
}
public function getSourceLanguageId(): ?int
{
return $this->sourceLanguageId;
}
public function getCode(): ?string
{
return $this->code;
}
}

View File

@@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\Admin\Project\ServiceTranslate;
final class Translations
{
private array $translations;
public function add(Translation $translation): void
{
$this->translations[] = $translation;
}
public function getTranslations(): array
{
return $this->translations;
}
}

View File

@@ -0,0 +1,17 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\Admin\Project\ServiceTranslate;
use App\Dto\Service\Dto;
final readonly class Update extends Dto
{
public function __construct(
private Translations $translations,
) { }
public function getTranslations(): Translations
{
return $this->translations;
}
}

View File

@@ -8,10 +8,16 @@ final readonly class Update extends Dto
{
public function __construct(
private Translations $translations,
private bool $isTranslateAutomatically = false,
) { }
public function getTranslations(): Translations
{
return $this->translations;
}
public function isTranslateAutomatically(): bool
{
return $this->isTranslateAutomatically;
}
}

View File

@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\ProjectTranslationServiceHash;
final class Fields
{
private array $fields = [];
public function add(string $name, string $value): self
{
$this->fields[$name] = $value;
return $this;
}
public function getFields(): array
{
return $this->fields;
}
public function getNames(): array
{
return \array_keys($this->fields);
}
}

View File

@@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\ProjectTranslationServiceHash;
final class HashStatusWaiting
{
private array $hash = [];
public function add(string $fieldName, bool $isWaiting): void
{
$this->hash[$fieldName] = $isWaiting;
}
public function isStatusWaiting(string $fieldName): bool
{
return $this->hash[$fieldName] ?? false;
}
}

View File

@@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\ProjectTranslationServiceHash;
final class Hashes
{
private array $hashes = [];
private array $ids = [];
public function add(int $hashId, string $fieldName, string $hash): void
{
$this->hashes[$hashId] = [
'field' => $fieldName,
'hash' => $hash,
];
$this->ids[] = $hashId;
}
public function getHash(int $hashId): ?array
{
return $this->hashes[$hashId] ?? null;
}
public function getIds(): array
{
return $this->ids;
}
}

View File

@@ -0,0 +1,37 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\ProjectTranslationServiceHash;
final class TranslateFields
{
private array $fields = [];
private array $hashes = [];
public function add(int $languageId, string $fieldName, string $hash, int $hashId): self
{
if (!isset($this->fields[$languageId])) {
$this->fields[$languageId] = [];
}
$this->fields[$languageId][] = $fieldName;
if (!isset($this->hashes[$languageId])) {
$this->hashes[$languageId] = [];
}
$this->hashes[$languageId][$fieldName] = [
'hash' => $hash,
'hashId' => $hashId,
];
return $this;
}
public function getFields(): array
{
return $this->fields;
}
public function getHashesByLanguage(int $languageId): array
{
return $this->hashes[$languageId] ?? [];
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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] ?? [];
}
}

View File

@@ -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,
};
}

View File

@@ -21,6 +21,9 @@ enum Permission: string
self::AdminPanel => [
'view' => __('permissions.Administrative panel allowed'),
],
self::Project => array_merge($this->getBasePermissions(), [
'Setting up automatic translation' => __('permissions.Setting up automatic translation'),
]),
self::ProjectContent => [
'view' => __('permissions.Allowed to watch'),
'create' => __('permissions.Allowed to create'),

View File

@@ -0,0 +1,9 @@
<?php declare(strict_types=1);
namespace App\Enums\ProjectTranslationServiceHashes;
enum Status: int
{
case Waiting = 0;
case Success = 10;
}

View File

@@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace App\Exceptions\Services\Translate;
final class CompletedException extends \Exception
{
}

View File

@@ -0,0 +1,41 @@
<?php declare(strict_types=1);
namespace App\Http\Controllers\Admin\Projects;
use App\Http\Controllers\Admin\Controller;
use App\Http\Requests\Admin\Projects\ServiceTranslate\UpdateRequest;
use App\Services\Admin\Project\ServiceTranslateService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
final class ServiceTranslateController extends Controller
{
public function __construct(
private readonly ServiceTranslateService $serviceTranslateService,
) { }
public function view(int $projectId, Request $request): View
{
$user = $request->user();
$result = $this->serviceTranslateService->view($projectId, $user);
if ($result->isError()) {
$this->errors($result);
}
return view('admin.projects.service-translate.view', $result->getData());
}
public function update(int $projectId, UpdateRequest $request): RedirectResponse
{
$data = $request->getDto();
$user = $request->user();
$result = $this->serviceTranslateService->update($projectId, $data, $user);
if ($result->isError()) {
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
}
return redirect()->route('admin.projects.service-translate.view', ['project' => $projectId])->withSuccess($result->getMessage());
}
}

View File

@@ -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),
);
}

View File

@@ -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;

View File

@@ -32,6 +32,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
'content.*.title' => ['required', 'string', 'max:255'],
'content.*.content' => ['nullable', 'string'],
'content.*.content_images.*.file' => ['numeric', 'min:1'],
'translate-automatically.*' => ['nullable', 'boolean'],
];
}
@@ -60,6 +61,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
title: $content['title'],
content: $content['content'] ?? '',
storages: $this->contentStorages($content),
isTranslateAutomatically: (bool) $this->input('translate-automatically.' . $languageId, false),
));
}
return $contents;

View File

@@ -0,0 +1,56 @@
<?php declare(strict_types=1);
namespace App\Http\Requests\Admin\Projects\ServiceTranslate;
use App\Contracts\FormRequestDto;
use App\Dto\Service\Admin\Project\ServiceTranslate\Translation;
use App\Dto\Service\Admin\Project\ServiceTranslate\Translations;
use App\Dto\Service\Admin\Project\ServiceTranslate\Update;
use Illuminate\Foundation\Http\FormRequest;
final class UpdateRequest extends FormRequest implements FormRequestDto
{
public function attributes(): array
{
return [
'language.*.id' => __('validation.attributes.language_id'),
'language.*.code' => __('validation.attributes.code'),
'language.*.source_language_id' => __('validation.attributes.source_language_id'),
];
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'language.*.id' => ['required', 'numeric', 'min:1'],
'language.*.code' => ['nullable', 'string', 'min:2', 'max:50'],
'language.*.source_language_id' => ['nullable', 'numeric', 'min:1', 'different:language.*.id'],
];
}
public function getDto(): Update
{
$translations = new Translations();
foreach ($this->input('language', []) as $language) {
$sourceLanguageId = $language['source_language_id'] ?? null;
if ($sourceLanguageId) {
$sourceLanguageId = (int) $sourceLanguageId;
}
$translation = new Translation(
languageId: (int) $language['id'],
sourceLanguageId: $sourceLanguageId,
code: $language['code'] ?? null,
);
$translations->add($translation);
}
return new Update(
translations: $translations,
);
}
}

View File

@@ -20,6 +20,7 @@ final class UpdateRequest extends FormRequest implements FormRequestDto
'translations' => ['nullable', 'array'],
'translations.*.code' => ['required', 'string', new In(Translations::getTranslationCodes())],
'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),
);
}
}

View File

@@ -0,0 +1,58 @@
<?php declare(strict_types=1);
namespace App\Jobs\Translate;
use App\Services\Translate\Project\ProjectContentService;
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 ProcessProjectContent 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 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());
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,58 @@
<?php declare(strict_types=1);
namespace App\Jobs\Translate;
use App\Services\Translate\Project\DocumentationContentService;
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 ProcessProjectDocumentationContent 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 $projectDocumentId,
private readonly array $contentIds = [],
private readonly array $exceptLanguages = [],
) { }
/**
* Get the unique ID for the job.
*/
public function uniqueId(): string
{
return 'documentation-' . $this->projectDocumentId;
}
/**
* Execute the job.
* @throws Exception
*/
public function handle(DocumentationContentService $documentationContentService): void
{
$result = $documentationContentService->translate($this->projectDocumentId, $this->contentIds, $this->exceptLanguages);
if ($result->isError() && $result->getCode() !== 404) {
cache()->lock($this->uniqueId())->forceRelease();
throw new Exception($result->getMessage());
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -17,5 +17,6 @@ final class ProjectContent extends Model implements StorageContract
protected $fillable = [
'title',
'description',
'language_id',
];
}

View File

@@ -7,6 +7,7 @@ use App\Models\Scopes\SortScope;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
@@ -70,4 +71,9 @@ final class ProjectLanguage extends Model
},
)->shouldCache();
}
public function serviceTranslate(): HasOne
{
return $this->hasOne(ProjectTranslationService::class, 'language_id', 'id');
}
}

View File

@@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
final class ProjectTranslationService extends Model
{
use HasFactory;
protected $table = 'project_translation_service';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'code',
'language_id',
'source_language_id',
];
}

View File

@@ -0,0 +1,51 @@
<?php declare(strict_types=1);
namespace App\Models;
use App\Enums\Morph;
use App\Enums\ProjectTranslationServiceHashes\Status;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
final class ProjectTranslationServiceHash extends Model
{
use HasFactory;
protected $table = 'project_translation_service_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',
'field',
'status',
'hash',
'morph_type',
'morph_id',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'status' => Status::class,
'morph_type' => Morph::class,
];
}
}

View File

@@ -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,
];
}
}

View File

@@ -32,6 +32,11 @@ final readonly class ProjectPolicy extends Policy
return $user->hasPermission('project.delete');
}
public function settingUpAutomaticTranslation(User $user, Project $project): bool
{
return $user->hasPermission('project.setting-up-automatic-translation');
}
public function upload(User $user): bool
{
if ($user->hasPermission('project.create') || $user->hasPermission('project.update')) {

View File

@@ -22,4 +22,13 @@ final readonly class ProjectTranslationRepository
$query = ProjectTranslation::query()->withTrashed()->where('project_id', $projectId)->where('language_id', $languageId);
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);
}
}

View File

@@ -0,0 +1,37 @@
<?php declare(strict_types=1);
namespace App\Repositories;
use App\Contracts\Search;
use App\Enums\Morph;
use App\Models\ProjectTranslationServiceHash;
use App\Services\Search\CreateSearchInstanceCommand;
use Illuminate\Database\Eloquent\Builder;
final readonly class ProjectTranslationServiceHashRepository
{
public function __construct(
private CreateSearchInstanceCommand $createSearchInstanceCommand,
) { }
public function getHashes(Morph $morph, int $morphId, ?array $languages = null, ?array $fields = null): Search
{
$query = ProjectTranslationServiceHash::query()
->where('morph_type', $morph)
->where('morph_id', $morphId)
->when($languages, function (Builder $query) use ($languages) {
$query->whereIn('language_id', $languages);
})
->when($fields, function (Builder $query) use ($fields) {
$query->whereIn('field', $fields);
});
return $this->createSearchInstanceCommand->execute($query);
}
public function getHashesByIds(array $ids): Search
{
$query = ProjectTranslationServiceHash::query()->whereIn('id', $ids);
return $this->createSearchInstanceCommand->execute($query);
}
}

View File

@@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace App\Repositories;
use App\Models\ProjectTranslationService;
use App\Services\Search\CreateSearchInstanceCommand;
use App\Services\Search\Search;
use Illuminate\Database\Eloquent\Builder;
final readonly class ProjectTranslationServiceRepository
{
public function __construct(
private CreateSearchInstanceCommand $createSearchInstanceCommand,
) { }
public function getLanguagesBySourceLanguage(int $sourceLanguage, ?array $excludeLanguages = null): Search
{
$query = ProjectTranslationService::query()
->select('language_id', 'code')
->where('source_language_id', $sourceLanguage)
->when($excludeLanguages, function (Builder $query) use ($excludeLanguages) {
$query->whereNotIn('language_id', $excludeLanguages);
});
return $this->createSearchInstanceCommand->execute($query);
}
public function getLanguageCodeByLanguageId(int $languageId): ?string
{
return ProjectTranslationService::query()
->select('code')
->where('language_id', $languageId)
->first()?->code ?? null;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -5,7 +5,10 @@ namespace App\Services\Admin\Project;
use App\Dto\Builder\Documentation as DocumentationBuilderDto;
use App\Dto\QuerySettingsDto;
use App\Dto\Service\Admin\Project\Documentation\StoreUpdate;
use App\Dto\Service\Admin\Project\DocumentationContent\Content;
use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
use App\Exceptions\Services\DocumentationContent\StorageCommandException;
use App\Jobs\Translate\ProcessProjectDocumentationContent;
use App\Models\Documentation;
use App\Models\ProjectLanguage;
use App\Models\User;
@@ -90,6 +93,7 @@ final class DocumentationService extends Service
'project' => $project,
'documentation' => new Documentation(),
'categories' => $this->documentationCategoryRepository->getForSelect($version, $defaultLanguage),
'serviceTranslationEnable' => config('translation_service.enable', false),
]);
}
@@ -120,6 +124,7 @@ final class DocumentationService extends Service
'project' => $project,
'documentation' => $documentation,
'categories' => $this->documentationCategoryRepository->getForSelect($version, $defaultLanguage, null, $withCategories),
'serviceTranslationEnable' => config('translation_service.enable', false),
]);
}
@@ -150,6 +155,10 @@ final class DocumentationService extends Service
return $documentation;
});
if (\config('translation_service.enable', false)) {
$this->translateContent($documentation, $data->getContents());
}
} catch (StorageCommandException $e) {
return $e->getResultError();
} catch (\Throwable $e) {
@@ -192,6 +201,10 @@ final class DocumentationService extends Service
return $documentation;
});
if (\config('translation_service.enable', false)) {
$this->translateContent($documentation, $data->getContents());
}
} catch (StorageCommandException $e) {
return $e->getResultError();
} catch (\Throwable $e) {
@@ -240,4 +253,27 @@ final class DocumentationService extends Service
'category_id' => $data->getCategoryId(),
];
}
private function translateContent(Documentation $documentation, 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 = $documentation->contents()->select('id')
->whereIn('language_id', $translateLanguages)
->get()->pluck('id')->toArray();
ProcessProjectDocumentationContent::dispatch($documentation->id, $translateContentIds, $translateExceptLanguages);
}
}

View File

@@ -0,0 +1,67 @@
<?php declare(strict_types=1);
namespace App\Services\Admin\Project;
use App\Dto\Service\Admin\Project\ServiceTranslate\Update;
use App\Models\User;
use App\Repositories\ProjectRepository;
use App\ServiceResults\ServiceResultArray;
use App\ServiceResults\ServiceResultError;
use App\ServiceResults\ServiceResultSuccess;
use App\Services\ProjectTranslationService\ModelSyncCommand as TranslationServiceModelSyncCommand;
use App\Services\Service;
use Illuminate\Support\Facades\DB;
final class ServiceTranslateService extends Service
{
public function __construct(
private readonly ProjectRepository $projectRepository,
private readonly TranslationServiceModelSyncCommand $translationServiceModelSyncCommand,
) { }
public function view(int $projectId, User $user): ServiceResultError | ServiceResultArray
{
$project = $this->projectRepository->getProjectById($projectId);
if (\is_null($project)) {
return $this->errNotFound(__('Not Found'));
}
if (
config('translation_service.enable', false) === false
|| $user->cannot('settingUpAutomaticTranslation', $project)
) {
return $this->errFobidden(__('Access is denied'));
}
return $this->result([
'project' => $project,
'languages' => $project->languages()->with(['serviceTranslate'])->get(),
]);
}
public function update(int $projectId, Update $data, User $user): ServiceResultError | ServiceResultSuccess
{
$project = $this->projectRepository->getProjectById($projectId);
if (\is_null($project)) {
return $this->errNotFound(__('Not Found'));
}
if (
config('translation_service.enable', false) === false
|| $user->cannot('settingUpAutomaticTranslation', $project)
) {
return $this->errFobidden(__('Access is denied'));
}
try {
DB::transaction(function () use ($data, $project) {
$this->translationServiceModelSyncCommand->execute($project, $data->getTranslations());
});
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
}
return $this->ok(__('The settings were saved successfully'));
}
}

View File

@@ -3,8 +3,10 @@
namespace App\Services\Admin\Project;
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\Enums\CacheTag;
use App\Jobs\Translate\ProcessTranslationText;
use App\Models\ProjectTranslation;
use App\Models\User;
use App\Repositories\ProjectRepository;
@@ -60,6 +62,7 @@ final class TranslationService extends Service
'language' => $language,
'projectTranslations' => $this->projectTranslationRepository->getProjectTranslations($projectId, $languageId)->all()->pluck('text', 'code')->toArray(),
'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->clearCacheCommandHandler->byTag(CacheTag::ProjectTranslation);
if (\config('translation_service.enable', false)) {
$this->translateContent($projectId, $languageId, $data);
}
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
@@ -88,4 +94,19 @@ final class TranslationService extends Service
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);
}
}

View File

@@ -50,6 +50,7 @@ final class ProjectService extends Service
return $this->result([
'projects' => $projects,
'serviceTranslationEnable' => config('translation_service.enable', false),
]);
}

View File

@@ -6,7 +6,7 @@ use App\Enums\CacheTag;
final readonly class ClearCacheCommandHandler
{
public function byTag(CacheTag $tag)
public function byTag(CacheTag $tag): void
{
$tag->getCache()->flush();
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,54 @@
<?php declare(strict_types=1);
namespace App\Services\ProjectTranslationService;
use App\Models\Project;
use App\Dto\Service\Admin\Project\ServiceTranslate\Translations;
use App\Models\ProjectTranslationService;
final readonly class ModelSyncCommand
{
public function execute(Project $project, Translations $translations): void
{
$insert = [];
$delete = [];
$languages = $project->languages()->with(['serviceTranslate'])->get();
foreach ($translations->getTranslations() as $translation) {
/* @var \App\Dto\Service\Admin\Project\ServiceTranslate\Translation $translation */
$language = $languages->firstWhere('id', '=', $translation->getLanguageId());
if ($language === null) {
continue;
}
$serviceTranslate = $language->serviceTranslate;
if ($serviceTranslate === null) {
if ($translation->getCode() !== null) {
$insert[] = [
'language_id' => $translation->getLanguageId(),
'code' => $translation->getCode(),
'source_language_id' => $translation->getSourceLanguageId(),
];
}
continue;
}
if ($translation->getCode() === null) {
$delete[] = [$serviceTranslate->id];
continue;
}
$serviceTranslate->update([
'code' => $translation->getCode(),
'source_language_id' => $translation->getSourceLanguageId(),
]);
}
if (!empty($insert)) {
ProjectTranslationService::query()->insert($insert);
}
if (!empty($delete)) {
ProjectTranslationService::query()->whereIn('id', $delete)->delete();
}
}
}

View File

@@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace App\Services\ProjectTranslationService;
use Illuminate\Support\Str;
final readonly class NoTranslateAttributeHandler
{
public function handleAddAttribute(string $text): string
{
return Str::replace('<code', '<code translate="no"', $text);
}
public function handleRemoveAttribute(string $text): string
{
return Str::replace('<code translate="no"', '<code', $text);
}
}

View File

@@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace App\Services\ProjectTranslationServiceHash;
use App\Dto\Service\ProjectTranslationServiceHash\Hashes;
use App\Dto\Service\ProjectTranslationServiceHash\HashStatusWaiting;
use App\Enums\ProjectTranslationServiceHashes\Status;
use App\Repositories\ProjectTranslationServiceHashRepository;
final readonly class CompletionChecker
{
public function __construct(
private ProjectTranslationServiceHashRepository $hashRepository,
private ProjectTranslationServiceHashCommandHandler $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->field === $dataHash['field']) {
$isWaiting = true;
$hashSuccessIds[] = $hash->id;
}
$hashStatusWaiting->add($hash->field, $isWaiting);
}
$this->commandHandler->handleSetStatusById($hashSuccessIds, Status::Success);
return $hashStatusWaiting;
}
}

View File

@@ -0,0 +1,69 @@
<?php declare(strict_types=1);
namespace App\Services\ProjectTranslationServiceHash;
use App\Dto\Service\ProjectTranslationServiceHash\Fields;
use App\Dto\Service\ProjectTranslationServiceHash\TranslateFields;
use App\Enums\Morph;
use App\Enums\ProjectTranslationServiceHashes\Status;
use App\Models\ProjectTranslationServiceHash;
use App\Repositories\ProjectTranslationServiceHashRepository;
final readonly class HashTrackerCommand
{
public function __construct(
private ProjectTranslationServiceHashRepository $hashRepository,
private ProjectTranslationServiceHashCommandHandler $commandHandler,
) { }
public function execute(Morph $morph, int $morphId, array $languages, Fields $fields): TranslateFields
{
$hashes = $this->hashRepository->getHashes($morph, $morphId, $languages, $fields->getNames())->all();
$translateFields = new TranslateFields();
foreach ($fields->getFields() as $fieldName => $fieldValue) {
$textHash = $this->generateHashFromText((string) $fieldValue);
foreach ($languages as $language) {
$modelHash = $hashes->where('language_id', $language)->firstWhere('field', $fieldName);
if ($modelHash === null) {
$modelHash = $this->commandHandler->handleStore([
'language_id' => $language,
'morph_type' => $morph,
'morph_id' => $morphId,
'field' => $fieldName,
'hash' => $textHash,
'status' => Status::Waiting,
]);
$translateFields->add($language, $fieldName, $textHash, $modelHash->id);
continue;
}
if ($modelHash->hash === $textHash && $this->isSuccess($modelHash)) {
continue;
}
$translateFields->add($language, $fieldName, $textHash, $modelHash->id);
$this->commandHandler->handleUpdate($modelHash, [
'hash' => $textHash,
'status' => Status::Waiting,
]);
}
}
return $translateFields;
}
private function generateHashFromText(string $text): string
{
return \hash('sha256', $text);
}
private function isSuccess(ProjectTranslationServiceHash $modelHash): bool
{
if ($modelHash->status === Status::Success) {
return true;
}
return ($modelHash->updated_at >= now()->subMinutes(10));
}
}

View File

@@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace App\Services\ProjectTranslationServiceHash;
use App\Enums\ProjectTranslationServiceHashes\Status;
use App\Models\ProjectTranslationServiceHash;
final readonly class ProjectTranslationServiceHashCommandHandler
{
public function handleStore(array $data): ProjectTranslationServiceHash
{
return ProjectTranslationServiceHash::create($data);
}
public function handleUpdate(ProjectTranslationServiceHash $hash, array $data): ProjectTranslationServiceHash
{
$hash->update($data);
$hash->touch();
return $hash;
}
public function handleSetStatusById(array $ids, Status $status): void
{
ProjectTranslationServiceHash::query()->whereIn('id', $ids)->update(['status' => $status->value]);
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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]);
}
}

View File

@@ -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 readonly 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;
}
}

View File

@@ -0,0 +1,90 @@
<?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\ProcessProjectDocumentationContent;
use App\Models\Documentation;
use App\Models\DocumentationContent;
use App\Repositories\DocumentationRepository;
use App\Services\ProjectTranslationService\NoTranslateAttributeHandler;
use App\Services\ProjectTranslationServiceHash\CompletionChecker;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener;
final readonly class DocumentationContentService implements TranslationCompletedListener
{
public function __construct(
private DocumentationRepository $documentationRepository,
private NoTranslateAttributeHandler $noTranslateAttributeHandler,
private CompletionChecker $completionChecker,
) { }
/**
* @throws CompletedException
*/
public function onTranslationCompleted(array $translatedText, array $data = []): void
{
if (
!isset($data['projectDocumentId'])
|| !isset($data['languageId'])
|| !isset($data['hashes'])
) {
throw new CompletedException('Required data is missing: projectDocumentId, languageId, or hashes.');
}
$documentation = $this->documentationRepository->getDocumentationById((int) $data['projectDocumentId']);
if ($documentation === null) {
return;
}
$documentationContent = DB::transaction(function () use ($data, $documentation, $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;
}
$documentationContent = $documentation->contents()->where('language_id', $data['languageId'])->first();
if ($documentationContent !== null) {
$documentationContent->update($values);
return $documentationContent;
}
$values['language_id'] = $data['languageId'];
return $documentation->contents()->create($values);
});
if (\config('translation_service.enable', false) && $documentationContent !== null) {
$this->translateContent($documentation, $documentationContent, $data);
}
}
private function translateContent(Documentation $documentation, DocumentationContent $documentationContent, array $data): void
{
$translateExceptLanguages = $data['exceptLanguages'] ?? [];
$translateExceptLanguages[] = $data['languageId'];
ProcessProjectDocumentationContent::dispatch($documentation->id, [$documentationContent->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;
}
}

View File

@@ -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\ProcessProjectContent;
use App\Models\Project;
use App\Models\ProjectContent;
use App\Repositories\ProjectRepository;
use App\Services\ProjectTranslationService\NoTranslateAttributeHandler;
use App\Services\ProjectTranslationServiceHash\CompletionChecker;
use Illuminate\Support\Facades\DB;
use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener;
final readonly class ProjectContentService implements TranslationCompletedListener
{
public function __construct(
private ProjectRepository $projectRepository,
private NoTranslateAttributeHandler $noTranslateAttributeHandler,
private CompletionChecker $completionChecker,
) { }
/**
* @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;
}
$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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,95 @@
<?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\DocumentationRepository;
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\Service;
use App\Services\Translate\Completed\Project\DocumentationContentService as DocumentationContentServiceCompleted;
use Illuminate\Support\Facades\DB;
use KorElf\TranslateLaravel\DTO\AfterTranslateDto;
use KorElf\TranslateLaravel\Facades\Translate;
final class DocumentationContentService extends Service
{
public function __construct(
private readonly DocumentationRepository $documentationRepository,
private readonly ProjectTranslationServiceRepository $projectTranslationServiceRepository,
private readonly HashTrackerCommand $hashTrackerCommand,
private readonly NoTranslateAttributeHandler $noTranslateAttributeHandler,
) {}
public function translate(int $projectDocumentId, array $contentIds, array $exceptLanguages): ServiceResultSuccess | ServiceResultError
{
try {
$documentation = $this->documentationRepository->getDocumentationById($projectDocumentId);
if ($documentation === null) {
return $this->errNotFound(__('Documentation not found'));
}
$contents = $documentation->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('content', $content->content ?? '');
$translations = DB::transaction(function () use ($content, $translateIntoLanguage, $fields) {
return $this->hashTrackerCommand->execute(Morph::DocumentationContent, $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(DocumentationContentServiceCompleted::class, [
'projectDocumentId' => $projectDocumentId,
'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();
}
}

View File

@@ -0,0 +1,95 @@
<?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\ProjectRepository;
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\Service;
use App\Services\Translate\Completed\Project\ProjectContentService as ProjectContentServiceCompleted;
use Illuminate\Support\Facades\DB;
use KorElf\TranslateLaravel\DTO\AfterTranslateDto;
use KorElf\TranslateLaravel\Facades\Translate;
final class ProjectContentService extends Service
{
public function __construct(
private readonly ProjectRepository $projectRepository,
private readonly ProjectTranslationServiceRepository $projectTranslationServiceRepository,
private readonly HashTrackerCommand $hashTrackerCommand,
private readonly NoTranslateAttributeHandler $noTranslateAttributeHandler,
) {}
public function translate(int $projectId, array $contentIds, array $exceptLanguages): ServiceResultSuccess | ServiceResultError
{
try {
$project = $this->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();
}
}

View File

@@ -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;
}
}

View File

@@ -8,6 +8,7 @@
"php": "^8.3",
"intervention/image-laravel": "^1.2",
"kor-elf/captcha-rule-for-laravel": "^1.0",
"kor-elf/translate-laravel": "1.3.0",
"laravel/framework": "^11.0",
"laravel/tinker": "^2.9",
"staudenmeir/laravel-adjacency-list": "^1.0"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Translate service
|--------------------------------------------------------------------------
|
| Enables or disables translate service.
*/
'enable' => (bool) env('TRANSLATION_SERVICE_ENABLE', false),
];

View File

@@ -0,0 +1,32 @@
<?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', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('language_id')->unique();
$table->foreign('language_id')->references('id')->on('project_languages');
$table->unsignedBigInteger('source_language_id')->nullable()->index();
$table->foreign('source_language_id')->references('id')->on('project_languages');
$table->string('code', 50);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('project_translation_service');
}
};

View File

@@ -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_translation_service_hashes', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('language_id')->index();
$table->foreign('language_id')->references('id')->on('project_languages');
$table->unsignedInteger('morph_type');
$table->unsignedBigInteger('morph_id');
$table->string('field', 255);
$table->unsignedInteger('status')->index()->default(0);
$table->char('hash', 64);
$table->timestamps();
$table->index(['morph_type', 'morph_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('project_translation_service_hashes');
}
};

View File

@@ -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');
}
};

View File

@@ -256,6 +256,7 @@
"The link has been deleted": "The link has been deleted",
"Language not found": "Language not found",
"Project not found": "Project not found",
"Documentation not found": "Documentation not found",
"Translations successfully updated": "Translations successfully updated",
"allowed characters:": "allowed characters:",
"Documentation version created successfully": "Documentation version created successfully",
@@ -268,5 +269,8 @@
"Category successfully created": "Category successfully created",
"Category updated successfully": "Category updated successfully",
"Category successfully deleted": "Category successfully deleted",
"Old Files deleted": "Old Files deleted"
"Old Files deleted": "Old Files deleted",
"Automatic translation": "Automatic translation",
"The settings were saved successfully": "The settings were saved successfully",
"Category not found": "Category not found"
}

View File

@@ -16,4 +16,5 @@ return [
'Documentation version' => 'Documentation version',
'Documentation' => 'Documentation',
'Categories' => 'Categories',
'Setting up automatic translation' => 'Setting up automatic translation',
];

View File

@@ -9,6 +9,8 @@ return [
'Allowed to edit' => 'Allowed to edit',
'Allowed to delete' => 'Allowed to delete',
'Setting up automatic translation' => 'Setting up automatic translation',
'Administrative panel allowed' => 'Administrative panel allowed',
'AdminPanel' => 'Administrative panel allowed',

View File

@@ -308,5 +308,9 @@ return [
'content.*.content' => 'content',
'category_id' => 'category',
'content_images' => 'content images',
'source_language_id' => 'source language identifier',
'translate_from_language' => 'translate from language',
'translate-automatically' => 'translate automatically',
'translate-automatically.*' => 'translate automatically',
],
];

View File

@@ -256,6 +256,7 @@
"The link has been deleted": "Ссылка удалена",
"Language not found": "Язык не найден",
"Project not found": "Проект не найден",
"Documentation not found": "Документация не найдена",
"Translations successfully updated": "Переводы успешно обновлены",
"allowed characters:": "разрешенные символы:",
"Documentation version created successfully": "Версия документации успешно создана",
@@ -268,5 +269,8 @@
"Category successfully created": "Категория успешно создана",
"Category updated successfully": "Категория успешно обновлена",
"Category successfully deleted": "Категория успешно удалена",
"Old Files deleted": "Старые файлы удалены"
"Old Files deleted": "Старые файлы удалены",
"Automatic translation": "Автоматический перевод",
"The settings were saved successfully": "Настройки успешно сохранены",
"Category not found": "Категория не найдена"
}

View File

@@ -16,4 +16,5 @@ return [
'Documentation version' => 'Версия документации',
'Documentation' => 'Документация',
'Categories' => 'Категории',
'Setting up automatic translation' => 'Настройка автоматического перевода',
];

View File

@@ -9,6 +9,8 @@ return [
'Allowed to edit' => 'Разрешено редактировать',
'Allowed to delete' => 'Разрешено удалять',
'Setting up automatic translation' => 'Настройка автоматического перевода',
'Administrative panel allowed' => 'Административная панель разрешена',
'AdminPanel' => 'Административная панель разрешена',

View File

@@ -308,5 +308,9 @@ return [
'content.*.content' => 'контент',
'category_id' => 'категория',
'content_images' => 'изображения контента',
'source_language_id' => 'идентификатор исходного языка',
'translate_from_language' => 'перевести с языка',
'translate-automatically' => 'переводить автоматически',
'translate-automatically.*' => 'переводить автоматически',
],
];

View File

@@ -1,4 +1,9 @@
@csrf
@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"/>
@endif
<x-volt.forms.input :title="__('validation.attributes.title')" name="title" type="text" :value="$content->title" required autofocus />
<x-volt.forms.textarea-wysiwyg
:title="__('validation.attributes.description')"

View File

@@ -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

View File

@@ -29,7 +29,13 @@
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"/>
not-checked-value="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/>

View File

@@ -40,6 +40,15 @@
@endif
</td>
<td>
@if($serviceTranslationEnable)
@can('settingUpAutomaticTranslation', $project)
<a href="{{ route('admin.projects.service-translate.view', $project) }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Automatic translation') }}">
<svg class="align-text-top" 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>
</svg>
</a>
@endcan
@endif
<a href="{{ route('admin.projects.edit', $project) }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Edit') }}">
<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"/>

View File

@@ -0,0 +1,46 @@
@section('meta_title', __('admin-sections.Setting up automatic translation') . '. ' . __('admin-sections.Project') . ': ' . $project->name)
@section('h1', __('admin-sections.Setting up automatic translation') . '. ' . __('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">
<form method="post" action="{{ route('admin.projects.service-translate.update', ['project' => $project->id]) }}">
@csrf
<div class="table-responsive">
<table class="table table-centered table-nowrap mb-0 rounded">
<thead class="thead-light">
<tr>
<th class="border-0">{{ __('validation.attributes.lang') }}</th>
<th class="border-0">{{ __('validation.attributes.code') }}</th>
<th class="border-0">{{ __('validation.attributes.translate_from_language') }}</th>
<th class="border-0 rounded-end" style="width: 150px"></th>
</tr>
</thead>
<tbody>
@foreach($languages as $index => $language)
<tr>
<td>{{ $language->title }}</td>
<td>
<x-volt.forms.input title="" name="language[{{ $index }}][code]" type="text" :value="$language->serviceTranslate?->code" />
</td>
<td>
<x-volt.forms.select title="" name="language[{{ $index }}][source_language_id]" :value="(string) $language->serviceTranslate?->source_language_id" :list="$languages->pluck('title', 'id')->toArray()">
<option value=""></option>
</x-volt.forms.select>
</td>
<td>
<input type="hidden" value="{{ $language->id }}" name="language[{{ $index }}][id]" />
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
</form>
</div>
</div>
</div>
</div>
</x-admin.layout>

View File

@@ -8,6 +8,11 @@
<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]) }}">
@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">
<thead class="thead-light">
<tr>

View File

@@ -1,5 +1,7 @@
<div class="mb-4">
<label for="form-select-{{ $requestName }}">{{ $title }}</label>
@if(!empty($title))
<label for="form-select-{{ $requestName }}">{{ $title }}</label>
@endif
<select id="form-select-{{ $requestName }}" aria-label="{{ $title }}" class="form-select @error($requestName) is-invalid @enderror" name="{{ $name }}" {{ $attributes }}>
{{ $slot }}
@foreach($list as $elementKey => $elementValue)

View File

@@ -25,6 +25,9 @@ Route::middleware(['auth', 'verified', \App\Http\Middleware\UserLocale::class])-
Route::get('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'edit'])->name('about.edit')->where(['language' => '[0-9]+']);
Route::put('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'update'])->name('about.update')->where(['language' => '[0-9]+']);
Route::get('service-translate', [\App\Http\Controllers\Admin\Projects\ServiceTranslateController::class, 'view'])->name('service-translate.view');
Route::post('service-translate', [\App\Http\Controllers\Admin\Projects\ServiceTranslateController::class, 'update'])->name('service-translate.update');
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');

View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -1,4 +1,4 @@
FROM docker.io/php:8.3-zts-alpine3.18 AS UNIT_BUILDER
FROM docker.io/php:8.3-zts-alpine3.18 AS unit_builder
ARG UNIT_VERSION=1.31.1
@@ -12,10 +12,10 @@ RUN apk --no-cache add pcre2-dev gcc git musl-dev make && \
make && \
make install
FROM docker.io/php:8.3-zts-alpine3.18 as BUILD
FROM docker.io/php:8.3-zts-alpine3.18 AS build
COPY --from=UNIT_BUILDER /var/sbin/unitd /usr/sbin/unitd
COPY --from=UNIT_BUILDER /var/lib/unit/ /var/lib/unit/
COPY --from=unit_builder /var/sbin/unitd /usr/sbin/unitd
COPY --from=unit_builder /var/lib/unit/ /var/lib/unit/
COPY docker/unit-config.json /docker-entrypoint.d/config.json
@@ -48,7 +48,7 @@ RUN apk --no-cache add pcre2 libbz2 libpng libwebp libjpeg-turbo icu-libs freety
&& ln -sf /dev/stdout /var/log/unit.log \
&& addgroup -S unit && adduser -S unit -G unit
FROM BUILD as APP_BUILD_FOR_PRODUCTION
FROM build AS app_build_for_production
WORKDIR /home/app
COPY application /home/app
@@ -60,9 +60,9 @@ RUN apk --no-cache add git nodejs npm \
&& rm -rf /home/app/node_modules /home/app/.env
FROM BUILD AS PRODUCTION
FROM build AS production
COPY --from=APP_BUILD_FOR_PRODUCTION /home/app /var/www/html
COPY --from=app_build_for_production /home/app /var/www/html
COPY docker/docker-entrypoint_prod.sh /home/unit/docker-entrypoint.sh
COPY docker/start.sh /usr/local/bin/start
@@ -78,7 +78,7 @@ EXPOSE 9000
CMD ["/usr/local/bin/start"]
FROM BUILD AS DEVELOP
FROM build AS develop
WORKDIR /var/www/html
@@ -99,22 +99,23 @@ CMD ["/usr/local/bin/start"]
FROM BUILD AS ARTISAN
FROM build AS artisan
WORKDIR /var/www/html
STOPSIGNAL SIGTERM
ENTRYPOINT ["php", "/var/www/html/artisan"]
FROM BUILD AS COMPOSER
FROM build AS composer
WORKDIR /var/www/html
STOPSIGNAL SIGTERM
RUN apk --no-cache add git \
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN mkdir "/.composer" && chmod -R 0777 "/.composer"
ENTRYPOINT ["composer"]
FROM BUILD AS NPM
FROM build AS npm
RUN mkdir "/.npm" && chmod -R 0777 "/.npm"
WORKDIR /var/www/html
STOPSIGNAL SIGTERM

View File

@@ -48,7 +48,6 @@ services:
app-redis:
image: redis:3.0-alpine # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
# restart: always
volumes:
- ./redis/data:/data
@@ -103,13 +102,11 @@ services:
env_file: captcha-app/.env
captcha-redis:
image: redis:3.0-alpine # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
# restart: always
volumes:
- ./captcha-app/redis/data:/data
db:
image: docker.io/mysql:8.0.33 # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/mysql:8.0.33 # MDHub
command: --default-authentication-plugin=mysql_native_password
#restart: always
ports:

View File

@@ -1,8 +1,8 @@
version: '3.7'
services:
app:
# image: korelf/my-projects-website:0.3.1 # docker hub
image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.3.1 # MDHub
# image: korelf/my-projects-website:0.4.0 # docker hub
image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.4.0 # MDHub
# restart: always
depends_on:
- db
@@ -17,10 +17,11 @@ services:
volumes:
- ./app/storage/app:/var/www/html/storage/app
- ./app/storage/logs:/var/www/html/storage/logs
# - ./app/translate/authorized_key.json:/var/www/html/storage/translation_service/authorized_key.json
queue:
# image: korelf/my-projects-website:0.3.1 # docker hub
image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.3.1 # MDHub
# image: korelf/my-projects-website:0.4.0 # docker hub
image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.4.0 # MDHub
# restart: always
depends_on:
- db
@@ -31,10 +32,11 @@ services:
volumes:
- ./app/storage/app:/var/www/html/storage/app
- ./app/storage/logs:/var/www/html/storage/logs
# - ./app/translate/authorized_key.json:/var/www/html/storage/translation_service/authorized_key.json
scheduler:
# image: korelf/my-projects-website:0.3.1 # docker hub
image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.3.1 # MDHub
# image: korelf/my-projects-website:0.4.0 # docker hub
image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.4.0 # MDHub
# restart: always
depends_on:
- db
@@ -45,10 +47,10 @@ services:
volumes:
- ./app/storage/app:/var/www/html/storage/app
- ./app/storage/logs:/var/www/html/storage/logs
# - ./app/translate/authorized_key.json:/var/www/html/storage/translation_service/authorized_key.json
app-redis:
image: redis:3.0-alpine # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
# restart: always
volumes:
- ./redis/data:/data
@@ -103,13 +105,11 @@ services:
env_file: captcha-app/.env
captcha-redis:
image: redis:3.0-alpine # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
# restart: always
volumes:
- ./captcha-app/redis/data:/data
db:
image: docker.io/mysql:8.0.33 # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/mysql:8.0.33 # MDHub
command: --default-authentication-plugin=mysql_native_password
#restart: always
ports:

View File

@@ -42,7 +42,6 @@ services:
app-redis:
image: redis:3.0-alpine # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
volumes:
- ./redis/data:/data
@@ -104,12 +103,10 @@ services:
- ./captcha-app/app/storage/logs:/var/www/html/storage/logs
captcha-redis:
image: redis:3.0-alpine # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
volumes:
- ./captcha-app/redis/data:/data
db:
image: docker.io/mysql:8.0.33 # docker hub
# image: docker.mdhub.kor-elf.net/kor-elf/mysql:8.0.33 # MDHub
command: --default-authentication-plugin=mysql_native_password
#restart: always
ports: