Версия 0.4.0 #7
@ -7,6 +7,7 @@
|
||||
**/storage/framework/sessions/*
|
||||
**/storage/framework/views/*
|
||||
**/storage/framework/testing/*
|
||||
**/storage/translation_service/*
|
||||
**/storage/logs/*
|
||||
**/vendor/
|
||||
**/node_modules/
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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'),
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
24
app/application/app/Models/ProjectTranslationService.php
Normal file
24
app/application/app/Models/ProjectTranslationService.php
Normal 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',
|
||||
];
|
||||
}
|
@ -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')) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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'));
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ final class ProjectService extends Service
|
||||
|
||||
return $this->result([
|
||||
'projects' => $projects,
|
||||
'serviceTranslationEnable' => config('translation_service.enable', false),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
3088
app/application/composer.lock
generated
3088
app/application/composer.lock
generated
File diff suppressed because it is too large
Load Diff
11
app/application/config/translation_service.php
Normal file
11
app/application/config/translation_service.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Translate service
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Enables or disables translate service.
|
||||
*/
|
||||
'enable' => (bool) env('TRANSLATION_SERVICE_ENABLE', false),
|
||||
];
|
@ -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');
|
||||
}
|
||||
};
|
@ -16,4 +16,5 @@ return [
|
||||
'Documentation version' => 'Documentation version',
|
||||
'Documentation' => 'Documentation',
|
||||
'Categories' => 'Categories',
|
||||
'Setting up automatic translation' => 'Setting up automatic translation',
|
||||
];
|
||||
|
@ -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',
|
||||
|
||||
|
@ -308,5 +308,8 @@ 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',
|
||||
],
|
||||
];
|
||||
|
@ -16,4 +16,5 @@ return [
|
||||
'Documentation version' => 'Версия документации',
|
||||
'Documentation' => 'Документация',
|
||||
'Categories' => 'Категории',
|
||||
'Setting up automatic translation' => 'Настройка автоматического перевода',
|
||||
];
|
||||
|
@ -9,6 +9,8 @@ return [
|
||||
'Allowed to edit' => 'Разрешено редактировать',
|
||||
'Allowed to delete' => 'Разрешено удалять',
|
||||
|
||||
'Setting up automatic translation' => 'Настройка автоматического перевода',
|
||||
|
||||
'Administrative panel allowed' => 'Административная панель разрешена',
|
||||
'AdminPanel' => 'Административная панель разрешена',
|
||||
|
||||
|
@ -308,5 +308,8 @@ return [
|
||||
'content.*.content' => 'контент',
|
||||
'category_id' => 'категория',
|
||||
'content_images' => 'изображения контента',
|
||||
'source_language_id' => 'идентификатор исходного языка',
|
||||
'translate_from_language' => 'перевести с языка',
|
||||
'translate-automatically.*' => 'переводить автоматически',
|
||||
],
|
||||
];
|
||||
|
@ -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>
|
@ -1,5 +1,7 @@
|
||||
<div class="mb-4">
|
||||
@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)
|
||||
|
@ -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');
|
||||
|
2
app/application/storage/translation_service/.gitignore
vendored
Executable file
2
app/application/storage/translation_service/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
Loading…
x
Reference in New Issue
Block a user