5 Commits

Author SHA1 Message Date
91be5052ed Fix missing new keyword in README code example. 2025-01-16 21:20:16 +05:00
e63ded1708 Replaced TranslationCompletedListener with AfterTranslateDto across translation-related classes to streamline and enhance the job chaining logic. Added validation to ensure the provided class implements the required interface and included support for additional contextual data in the translation process. This change improves flexibility and simplifies the translation workflow. 2025-01-16 21:04:46 +05:00
31da4eff78 Update .gitattributes export-ignore rules.
Adjusted export-ignore rules to include `.git`, `.gitignore`, and `.gitattributes` for more precise exclusions during archive creation. This ensures irrelevant files are omitted when packaging the repository.
2024-12-10 23:06:10 +05:00
5abddfbd57 Add .gitattributes file for export-ignore rules.
This ensures that the .git directory and .gitignore file are excluded when exporting the repository, improving distribution cleanliness. Including this file standardizes export behavior across clones.
2024-12-10 22:42:30 +05:00
7e05777385 Add LogDriver for translation with configuration support.
Introduced a new LogDriver for logging translation operations. Updated the README and configuration file to include support for the LogDriver and its associated limits. This feature enables the use of a logging-based translation driver for testing and debugging purposes.
2024-12-10 21:54:11 +05:00
10 changed files with 162 additions and 29 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
/.git export-ignore
/.gitignore export-ignore
/.gitattributes export-ignore

View File

@@ -5,7 +5,10 @@
# Параметры в **.env**: # Параметры в **.env**:
### TRANSLATE_SERVICE ### TRANSLATE_SERVICE
По умолчанию через какой сервис обращаться за переводом. На данный момент доступен только один сервис - **yandex**. По умолчанию через какой сервис обращаться за переводом. На данный момент доступен только сервисы - **yandex**, **log**.
**yandex** - https://yandex.cloud/ru/services/translate<br>
**log** - для проверки и внедрения в свой продукт.
### TRANSLATE_YANDEX_FOLDER_ID ### TRANSLATE_YANDEX_FOLDER_ID
ID folder. Код можно увидеть в адресе console.yandex.cloud/folders/**{тут будет код}**. ID folder. Код можно увидеть в адресе console.yandex.cloud/folders/**{тут будет код}**.
@@ -23,6 +26,16 @@ ID folder. Код можно увидеть в адресе console.yandex.cloud
### TRANSLATE_YANDEX_LIMIT_MAX_SYMBOLS ### TRANSLATE_YANDEX_LIMIT_MAX_SYMBOLS
Максимальное количество символов за один запрос. Если превышает, то делится на две части и делает по очереди два запроса. По умолчанию стоит 9000 символов. **На данный момент работает только перевод через систему очередей.** Максимальное количество символов за один запрос. Если превышает, то делится на две части и делает по очереди два запроса. По умолчанию стоит 9000 символов. **На данный момент работает только перевод через систему очередей.**
### TRANSLATE_LOG_LIMIT_MAX_REQUEST
Максимальное количество запросов в период, который указан в TRANSLATE_YANDEX_LIMIT_RATE_SECONDS. По умолчанию 20 запросов в секунду. **На данный момент работает только перевод через систему очередей.**
### TRANSLATE_LOG_LIMIT_RATE_SECONDS
Период в котором считается максимальное количество запросов. По умолчанию стоит секунда. **На данный момент работает только перевод через систему очередей.**
### TRANSLATE_LOG_LIMIT_MAX_SYMBOLS
Максимальное количество символов за один запрос. Если превышает, то делится на две части и делает по очереди два запроса. По умолчанию стоит 9000 символов. **На данный момент работает только перевод через систему очередей.**
# Методы # Методы
### Перевести обычный текст ### Перевести обычный текст
> \KorElf\TranslateLaravel\Facades\Translate::translateText( > \KorElf\TranslateLaravel\Facades\Translate::translateText(
@@ -57,12 +70,12 @@ ID folder. Код можно увидеть в адресе console.yandex.cloud
### Перевести с помощью очередей (Queues) ### Перевести с помощью очередей (Queues)
> \KorElf\TranslateLaravel\Facades\Translate::runJob( > \KorElf\TranslateLaravel\Facades\Translate::runJob(
> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\KorElf\TranslateLaravel\DTO\RunTranslateDto &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\$params, > <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\KorElf\TranslateLaravel\DTO\RunTranslateDto &nbsp;&nbsp;\$params,
> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\KorElf\TranslateLaravel\Contracts\TranslationCompletedListener \$completedListener > <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\KorElf\TranslateLaravel\DTO\AfterTranslateDto &nbsp;\$afterTranslateDto
<br>): \Illuminate\Foundation\Bus\PendingDispatch <br>): \Illuminate\Foundation\Bus\PendingDispatch
**\$params** - параметры перевода. **\$params** - параметры перевода.
<br>**\$completedListener** - после завершения перевода отправляет результат в этот объект. Объект должен соблюдать контракт **\KorElf\TranslateLaravel\Contracts\TranslationCompletedListener**. <br>**\$afterTranslateDto** - после завершения перевода отправляет результат в объект \$afterTranslateDto->\$className. Объект должен соблюдать контракт **\KorElf\TranslateLaravel\Contracts\TranslationCompletedListener**.
**Пример:** **Пример:**
> Создаём файл TranslationListener например в папке app/Services > Создаём файл TranslationListener например в папке app/Services
@@ -76,25 +89,41 @@ ID folder. Код можно увидеть в адресе console.yandex.cloud
> >
> final class TranslationListener implements TranslationCompletedListener > final class TranslationListener implements TranslationCompletedListener
> { > {
> public function onTranslationCompleted(array $translatedText): void > /**
> * Объекты создаются и пробрасываются автоматически через контейнер `$application->make`,
> * благодаря механизму автоматического внедрения зависимостей (Dependency Injection).
> * Вы можете передавать свои собственные классы, поместив их в качестве параметров конструктора.
> * Контейнер создаст эти объекты автоматически, основываясь на разрешении типа (type-hint).
> */
> // public function __construct(
> // private readonly SaveContentCommand $saveContentCommand
> // ) { }
>
> public function onTranslationCompleted(array $translatedText, array $data = []): void
> { > {
> Log::info($data);
> foreach ($translatedText as $translatedTextKey => $translatedTextValue) { > foreach ($translatedText as $translatedTextKey => $translatedTextValue) {
> Log::info($translatedTextKey . ': ' . $translatedTextValue); > Log::info($translatedTextKey . ': ' . $translatedTextValue);
> } > }
> // $this->saveContentCommand->execute($data['contentId'], $translatedText);
> } > }
> } > }
> >
> Потом например в каком-то контроллере (а лучше конечно в сервисе) пишем такой метод > Потом например в каком-то контроллере (а лучше конечно в сервисе) пишем такой метод
> >
> public function sendingForTranslation(\App\Services\TranslationListener $translationListener): View > public function sendingForTranslation(): View
> { > {
> // Вначале создаём объект с параметрами > // Вначале создаём объект с параметрами
> $params = (new \KorElf\TranslateLaravel\DTO\RunTranslateDto) > $params = (new \KorElf\TranslateLaravel\DTO\RunTranslateDto)
> ->addParamText('title', 'Заголовок', 'en', 'ru') > ->addParamText('title', 'Заголовок', 'en', 'ru')
> ->addParamHtml('content', '<p>Привет, Мир!</p>', 'en', 'ru'); > ->addParamHtml('content', '<p>Привет, Мир!</p>', 'en', 'ru');
> >
> $translationCompletedListener = \App\Services\TranslationListener::class;
> $data = ['contentId' => 1];
> $afterTranslateDto = new \KorElf\TranslateLaravel\DTO\AfterTranslateDto($translationCompletedListener, $data);
>
> // Отправляем на очередь > // Отправляем на очередь
> \KorElf\TranslateLaravel\Facades\Translate::runJob($params, $translationListener); > \KorElf\TranslateLaravel\Facades\Translate::runJob($params, $afterTranslateDto);
> >
> return view('success'); > return view('success');
> } > }

View File

@@ -35,5 +35,16 @@ return [
], ],
], ],
], ],
'log' => [
'driver' => '\KorElf\TranslateLaravel\Translate\LogDriver',
'config' => [
'limit' => [
'max_request' => (int) env('TRANSLATE_LOG_LIMIT_MAX_REQUEST', 20),
'rate_seconds' => (int) env('TRANSLATE_LOG_LIMIT_RATE_SECONDS', 1),
'max_symbols' => (int) env('TRANSLATE_LOG_LIMIT_MAX_SYMBOLS', 9000),
],
],
],
], ],
]; ];

View File

@@ -4,5 +4,5 @@ namespace KorElf\TranslateLaravel\Contracts;
interface TranslationCompletedListener interface TranslationCompletedListener
{ {
public function onTranslationCompleted(array $translatedText): void; public function onTranslationCompleted(array $translatedText, array $data = []): void;
} }

View File

@@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace KorElf\TranslateLaravel\DTO;
final readonly class AfterTranslateDto
{
/**
* @param string $className The fully qualified class name (FQN) of a class that implements the TranslationCompletedListener interface.
* @param array $data Additional data associated with the translation process.
*/
public function __construct(
private string $className,
private array $data = [],
) { }
/**
* Get the fully qualified class name (FQN) of a class that implements the TranslationCompletedListener interface.
*
* @return class-string<\KorElf\TranslateLaravel\Contracts\TranslationCompletedListener>
*/
public function getClassName(): string
{
return $this->className;
}
public function getData(): array
{
return $this->data;
}
}

View File

@@ -3,7 +3,7 @@
namespace KorElf\TranslateLaravel\Facades; namespace KorElf\TranslateLaravel\Facades;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;
use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener; use KorElf\TranslateLaravel\DTO\AfterTranslateDto;
use KorElf\TranslateLaravel\DTO\RunTranslateDto; use KorElf\TranslateLaravel\DTO\RunTranslateDto;
/** /**
@@ -11,7 +11,7 @@ use KorElf\TranslateLaravel\DTO\RunTranslateDto;
* @method static \KorElf\TranslateLaravel\Contracts\Translate resolve(string $name) * @method static \KorElf\TranslateLaravel\Contracts\Translate resolve(string $name)
* @method static string|array translateText(string|array $text, string $targetLanguageCode, ?string $sourceLanguageCode = null) * @method static string|array translateText(string|array $text, string $targetLanguageCode, ?string $sourceLanguageCode = null)
* @method static string|array translateHtml(string|array $text, string $targetLanguageCode, ?string $sourceLanguageCode = null) * @method static string|array translateHtml(string|array $text, string $targetLanguageCode, ?string $sourceLanguageCode = null)
* @method static \Illuminate\Foundation\Bus\PendingDispatch runJob(RunTranslateDto $params, TranslationCompletedListener $completedListener) * @method static \Illuminate\Foundation\Bus\PendingDispatch runJob(RunTranslateDto $params, AfterTranslateDto $afterTranslateDto)
* @method static \KorElf\TranslateLaravel\DTO\Languages listLanguages() * @method static \KorElf\TranslateLaravel\DTO\Languages listLanguages()
* @method static string getDefaultDriver() * @method static string getDefaultDriver()
* @method static void setDefaultDriver(string $name) * @method static void setDefaultDriver(string $name)

View File

@@ -5,10 +5,11 @@ namespace KorElf\TranslateLaravel\Jobs;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener; use KorElf\TranslateLaravel\DTO\AfterTranslateDto;
use KorElf\TranslateLaravel\DTO\Translated; use KorElf\TranslateLaravel\DTO\Translated;
use KorElf\TranslateLaravel\Exceptions\AfterTranslateException; use KorElf\TranslateLaravel\Exceptions\AfterTranslateException;
@@ -20,15 +21,12 @@ final class AfterTranslate implements ShouldQueue, ShouldBeEncrypted
* Create a new job instance. * Create a new job instance.
*/ */
public function __construct( public function __construct(
private readonly string $groupName, private readonly string $groupName,
private readonly TranslationCompletedListener $listener, private readonly Translated $translated,
private readonly Translated $translated, private readonly AfterTranslateDto $afterTranslateDto
) { } ) { }
/** public function handle(Application $application): void
* Execute the job.
*/
public function handle(): void
{ {
$translated = []; $translated = [];
$data = Cache::get($this->groupName, []); $data = Cache::get($this->groupName, []);
@@ -43,11 +41,15 @@ final class AfterTranslate implements ShouldQueue, ShouldBeEncrypted
$translated[$key] = implode(' ', $data[$key]); $translated[$key] = implode(' ', $data[$key]);
} }
if (empty($data)) { if (empty($data) || !empty($errors)) {
throw new AfterTranslateException('Part or all of the text has not been translated. Keys: ' . implode(', ', $errors)); throw new AfterTranslateException('Part or all of the text has not been translated. Keys: ' . implode(', ', $errors));
} }
Cache::forget($this->groupName); $objectAfterTranslate = $application->make($this->afterTranslateDto->getClassName());
if (!$objectAfterTranslate instanceof \KorElf\TranslateLaravel\Contracts\TranslationCompletedListener) {
throw new AfterTranslateException('The class must implement the \KorElf\TranslateLaravel\Contracts\TranslationCompletedListener interface.');
}
$objectAfterTranslate->onTranslationCompleted($translated, $this->afterTranslateDto->getData());
$this->listener->onTranslationCompleted($translated); Cache::forget($this->groupName);
} }
} }

View File

@@ -10,7 +10,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener; use KorElf\TranslateLaravel\DTO\AfterTranslateDto;
use KorElf\TranslateLaravel\DTO\ProcessTranslateDto; use KorElf\TranslateLaravel\DTO\ProcessTranslateDto;
use korElf\TranslateLaravel\DTO\RunTranslateDto; use korElf\TranslateLaravel\DTO\RunTranslateDto;
use KorElf\TranslateLaravel\DTO\Translated; use KorElf\TranslateLaravel\DTO\Translated;
@@ -24,8 +24,8 @@ final class RunTranslate implements ShouldQueue, ShouldBeEncrypted
* Create a new job instance. * Create a new job instance.
*/ */
public function __construct( public function __construct(
private readonly RunTranslateDto $runTranslateDto, private readonly RunTranslateDto $runTranslateDto,
private readonly TranslationCompletedListener $translationCompletedListener, private readonly AfterTranslateDto $afterTranslateDto,
) { } ) { }
/** /**
@@ -55,7 +55,7 @@ final class RunTranslate implements ShouldQueue, ShouldBeEncrypted
} }
$translated->add($key, count($texts)); $translated->add($key, count($texts));
} }
$chains[] = new AfterTranslate($groupName, $this->translationCompletedListener, $translated); $chains[] = new AfterTranslate($groupName, $translated, $this->afterTranslateDto);
Bus::chain($chains)->dispatch(); Bus::chain($chains)->dispatch();
} }

View File

@@ -0,0 +1,50 @@
<?php declare(strict_types=1);
namespace KorElf\TranslateLaravel\Translate;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Log;
use KorElf\TranslateLaravel\Contracts\Translate;
use KorElf\TranslateLaravel\DTO\Languages;
final class LogDriver implements Translate
{
/**
* @throws BindingResolutionException
*/
public static function init(Application $app, array $config = []): Translate
{
return new self(
$app->make(Log::class),
);
}
public function __construct(
private readonly Log $log
) { }
public function translateText(array|string $text, string $targetLanguageCode, ?string $sourceLanguageCode = null): string|array
{
$this->log::info('Translate text', ['text' => $text, 'targetLanguageCode' => $targetLanguageCode, 'sourceLanguageCode' => $sourceLanguageCode]);
return $text;
}
public function translateHtml(array|string $text, string $targetLanguageCode, ?string $sourceLanguageCode = null): string|array
{
$this->log::info('Translate html', ['text' => $text, 'targetLanguageCode' => $targetLanguageCode, 'sourceLanguageCode' => $sourceLanguageCode]);
return $text;
}
public function listLanguages(): Languages
{
$languages = new Languages();
$languages->add('ru', 'Russian');
$languages->add('en', 'English');
$languages->add('de', 'Deutsch');
return $languages;
}
}

View File

@@ -4,10 +4,10 @@ namespace KorElf\TranslateLaravel\Translate;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Bus\PendingDispatch; use Illuminate\Foundation\Bus\PendingDispatch;
use KorElf\TranslateLaravel\Contracts\CallbackAfterTranslated;
use KorElf\TranslateLaravel\Contracts\Translate;
use InvalidArgumentException; use InvalidArgumentException;
use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener; use KorElf\TranslateLaravel\Contracts\TranslationCompletedListener;
use KorElf\TranslateLaravel\Contracts\Translate;
use KorElf\TranslateLaravel\DTO\AfterTranslateDto;
use KorElf\TranslateLaravel\DTO\RunTranslateDto; use KorElf\TranslateLaravel\DTO\RunTranslateDto;
use KorElf\TranslateLaravel\Jobs\RunTranslate; use KorElf\TranslateLaravel\Jobs\RunTranslate;
@@ -68,9 +68,15 @@ final class TranslateManager
/** /**
* Run through queues. * Run through queues.
*/ */
public function runJob(RunTranslateDto $params, TranslationCompletedListener $completedListener): PendingDispatch public function runJob(RunTranslateDto $params, AfterTranslateDto $afterTranslateDto): PendingDispatch
{ {
return RunTranslate::dispatch($params, $completedListener); $className = $afterTranslateDto->getClassName();
if (!is_subclass_of($className, TranslationCompletedListener::class)) {
throw new InvalidArgumentException('The class ' . $className . ' must implement the \KorElf\TranslateLaravel\Contracts\Translate\TranslationCompletedListener interface.');
}
unset($className);
return RunTranslate::dispatch($params, $afterTranslateDto);
} }
public function getLimit(?string $driver = null): array public function getLimit(?string $driver = null): array