diff --git a/composer.json b/composer.json index ca40167..0f9d556 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,8 @@ "nikic/php-parser": "^v5.2", "google/cloud-translate": "^1.17", "openai-php/client": "^0.10.1", - "deeplcom/deepl-php": "^1.9" + "deeplcom/deepl-php": "^1.9", + "stichoza/google-translate-php": "^5.2" }, "scripts": { "test": "vendor/bin/phpunit tests" diff --git a/composer.lock b/composer.lock index b403ad4..3d1fb91 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7939a7b956d5a31121e191f5aebdc1ea", + "content-hash": "5495b74742400dc11f8c495cf182aa2c", "packages": [ { "name": "brick/math", @@ -1924,6 +1924,87 @@ ], "time": "2024-08-30T07:09:40+00:00" }, + { + "name": "stichoza/google-translate-php", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/Stichoza/google-translate-php.git", + "reference": "9429773d991c98f68a25bec40d20f590ea3312a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Stichoza/google-translate-php/zipball/9429773d991c98f68a25bec40d20f590ea3312a0", + "reference": "9429773d991c98f68a25bec40d20f590ea3312a0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/guzzle": "^7.0", + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Stichoza\\GoogleTranslate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Levan Velijanashvili", + "email": "me@stichoza.com" + } + ], + "description": "Free Google Translate API PHP Package", + "homepage": "https://github.com/Stichoza/google-translate-php", + "keywords": [ + "google", + "php", + "translate", + "translating", + "translator" + ], + "support": { + "issues": "https://github.com/Stichoza/google-translate-php/issues", + "source": "https://github.com/Stichoza/google-translate-php/tree/v5.2.0" + }, + "funding": [ + { + "url": "https://btc.com/bc1qc25j4x7yahghm8nnn6lypnw59nptylsw32nkfl", + "type": "custom" + }, + { + "url": "https://www.paypal.me/stichoza", + "type": "custom" + }, + { + "url": "https://ko-fi.com/stichoza", + "type": "ko_fi" + }, + { + "url": "https://liberapay.com/stichoza", + "type": "liberapay" + }, + { + "url": "https://opencollective.com/stichoza", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/stichoza", + "type": "patreon" + } + ], + "time": "2024-08-05T19:11:36+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v3.5.0", diff --git a/config/translator.php b/config/translator.php index 839d2ff..140ffb1 100644 --- a/config/translator.php +++ b/config/translator.php @@ -13,8 +13,11 @@ | Supported: "google", "openai", "deepl" | */ - 'default' => env('DEFAULT_TRANSLATOR_SERVICE', 'openai'), + 'default' => env('DEFAULT_TRANSLATOR_SERVICE', 'free_google'), 'translators' => [ + 'free_google' => [ + 'driver' => Bottelet\TranslationChecker\Translator\FreeGoogleTranslator::class, + ], 'google' => [ 'driver' => Bottelet\TranslationChecker\Translator\GoogleTranslator::class, 'type' => env('GOOGLE_TRANSLATE_TYPE', 'service_account'), diff --git a/docs/translation-services/free-google.md b/docs/translation-services/free-google.md new file mode 100644 index 0000000..7232092 --- /dev/null +++ b/docs/translation-services/free-google.md @@ -0,0 +1,12 @@ +--- +title: Free Google +layout: default +parent: Translation Services +nav_order: 0 +--- + +# Google Translation API + +Access Google Translate free of charge, without needing to set up API keys. + +Based on this [powerful package](https://github.com/Stichoza/google-translate-php) diff --git a/docs/translation-services/google.md b/docs/translation-services/google.md index ea0b7e8..f54c7ee 100644 --- a/docs/translation-services/google.md +++ b/docs/translation-services/google.md @@ -5,9 +5,11 @@ parent: Translation Services nav_order: 1 --- -# Google Translation API +# Google Cloud Translation API + +For Google Cloud Translate (More advanced translation than free Google previously), you need to set the following +environment variables -For Google Translate, you need to set the following environment variables ```bash GOOGLE_TRANSLATE_TYPE=service_account GOOGLE_TRANSLATE_PROJECT_ID=your_project_id diff --git a/src/TranslationCheckerServiceProvider.php b/src/TranslationCheckerServiceProvider.php index 36404cd..027c062 100644 --- a/src/TranslationCheckerServiceProvider.php +++ b/src/TranslationCheckerServiceProvider.php @@ -5,6 +5,7 @@ use Bottelet\TranslationChecker\Sort\AlphabeticSort; use Bottelet\TranslationChecker\Sort\SorterContract; use Bottelet\TranslationChecker\Translator\DeeplTranslator; +use Bottelet\TranslationChecker\Translator\FreeGoogleTranslator; use Bottelet\TranslationChecker\Translator\GoogleTranslator; use Bottelet\TranslationChecker\Translator\OpenAiTranslator; use Bottelet\TranslationChecker\Translator\TranslatorContract; @@ -37,6 +38,13 @@ public function register(): void ); }); + $this->app->bind(FreeGoogleTranslator::class, function ($app) { + return new FreeGoogleTranslator( + $app->make(VariableRegexHandler::class), + new \Stichoza\GoogleTranslate\GoogleTranslate() + ); + }); + $this->app->bind(OpenAiTranslator::class, function ($app) { $factory = OpenAI::factory(); if ($app->config['translator.translators.openai.api_key']) { diff --git a/src/Translator/FreeGoogleTranslator.php b/src/Translator/FreeGoogleTranslator.php new file mode 100644 index 0000000..a654b5f --- /dev/null +++ b/src/Translator/FreeGoogleTranslator.php @@ -0,0 +1,78 @@ +variableHandler->replacePlaceholders($text); + + $translation = $this->translateClient + ->setSource($sourceLanguage) + ->setTarget($targetLanguage) + ->translate($replaceVariablesText); + + if (!isset($translation)) { + return ''; + } + + return $this->variableHandler->restorePlaceholders($translation); + } + + /** + * @param array $texts Array of texts to translate. + * @param string $targetLanguage + * @param string $sourceLanguage + * @return array Array of translated texts. + * + * @throws LargeTextException + * @throws RateLimitException + * @throws TranslationRequestException + */ + public function translateBatch(array $texts, string $targetLanguage, string $sourceLanguage = 'en'): array + { + $textsToTranslate = array_map([$this->variableHandler, 'replacePlaceholders'], $texts); + + $translations = []; + foreach ($textsToTranslate as $text) { + $translations[] = $this->translateClient + ->setSource($sourceLanguage) + ->setTarget($targetLanguage) + ->translate($text); + } + + $translatedKeys = []; + foreach ($translations as $index => $translation) { + $translatedText = $translation ? $this->variableHandler->restorePlaceholders($translation) : ''; + $translatedKeys[$texts[$index]] = $translatedText; + } + + return $translatedKeys; + } + + public function isConfigured(): bool + { + /** @var array $freeGoogleConfig */ + $freeGoogleConfig = config('translator.translators.free_google'); + + return !in_array(null, $freeGoogleConfig, true); + } +} diff --git a/tests/Translator/FreeGoogleTranslatorTest.php b/tests/Translator/FreeGoogleTranslatorTest.php new file mode 100644 index 0000000..7fb4642 --- /dev/null +++ b/tests/Translator/FreeGoogleTranslatorTest.php @@ -0,0 +1,132 @@ +translateClientMock = $this->createMock(\Stichoza\GoogleTranslate\GoogleTranslate::class); + $this->variableHandlerMock = $this->createMock(VariableRegexHandler::class); + $this->freeGoogleTranslator = new FreeGoogleTranslator($this->variableHandlerMock, $this->translateClientMock); + } + + #[Test] + public function freeGoogleTranslateIfTextKeyNotReturned(): void + { + $this->translateClientMock->method('translate')->willReturn(null); + + $this->variableHandlerMock->method('restorePlaceholders')->willReturn('Translated text'); + + $result = $this->freeGoogleTranslator->translate('Hello', 'fr', 'en'); + + $this->assertEquals('', $result); + } + + #[Test] + public function translate(): void + { + $text = 'Hello, world!'; + $translatedText = 'Bonjour le monde!'; + $targetLanguage = 'fr'; + + $this->variableHandlerMock + ->method('replacePlaceholders') + ->willReturn($text); + + $this->variableHandlerMock + ->method('restorePlaceholders') + ->willReturn($translatedText); + + $this->translateClientMock + ->method('setSource') + ->with('en') + ->willReturn($this->translateClientMock); + + $this->translateClientMock + ->method('setTarget') + ->with($targetLanguage) + ->willReturn($this->translateClientMock); + + $this->translateClientMock + ->method('translate') + ->with($text) + ->willReturn($translatedText); + + $result = $this->freeGoogleTranslator->translate($text, $targetLanguage); + + $this->assertSame($translatedText, $result); + } + + #[Test] + public function translateBatch(): void + { + $texts = ['Hello, world!', 'Good morning']; + $translatedTexts = ['Bonjour le monde!', 'Bonjour']; + $targetLanguage = 'fr'; + $sourceLanguage = 'en'; + + $this->variableHandlerMock + ->expects($this->exactly(count($texts))) + ->method('replacePlaceholders') + ->willReturnArgument(0); + + $this->variableHandlerMock + ->expects($this->exactly(count($texts))) + ->method('restorePlaceholders') + ->willReturnArgument(0); + + $this->translateClientMock + ->expects($this->exactly(count($texts))) + ->method('setSource') + ->with($sourceLanguage) + ->willReturnSelf(); + + $this->translateClientMock + ->expects($this->exactly(count($texts))) + ->method('setTarget') + ->with($targetLanguage) + ->willReturnSelf(); + + $this->translateClientMock->expects($this->exactly(count($texts))) + ->method('translate') + ->willReturnOnConsecutiveCalls(...$translatedTexts); + + $result = $this->freeGoogleTranslator->translateBatch($texts, $targetLanguage, $sourceLanguage); + + $this->assertSame( + [ + 'Hello, world!' => 'Bonjour le monde!', + 'Good morning' => 'Bonjour'], + $result + ); + } + + #[Test] + public function testGoogleTranslatorBinding(): void + { + $this->assertInstanceOf(GoogleTranslator::class, app(GoogleTranslator::class)); + } + + #[Test] + public function testGoogleTranslatorHasValidCredentials(): void + { + $this->assertTrue($this->freeGoogleTranslator->isConfigured()); + } +}