Files
kupshop/bundles/KupShop/I18nBundle/Util/TranslationEngine.php
2025-08-02 16:30:27 +02:00

177 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
namespace KupShop\I18nBundle\Util;
use DeepL\DeepLException;
use DeepL\Translator;
class TranslationEngine
{
private const GOOGLE_CHUNK = 3900;
private static string $googleApiKey = 'AIzaSyCERmGVBqiuzN74C9l-CEMA08KwIgSQY_Y';
private static string $deepLAuthKey = '491e37c3-4dc9-ed7b-7188-536b0598f034';
public function getTranslation(string $text, string $sourceLanguage, string $language, bool $html = false): array
{
if (isFunctionalTests()) {
$responseData['translatedText'] = $sourceLanguage.'->'.$language.': '.$text;
return $responseData;
}
$deepl = (\Settings::getDefault()->use_deepl ?? 'N');
getLogger()->notice('TranslationEngine:translate', ['char_count' => mb_strlen($text), 'from' => $sourceLanguage, 'to' => $language, 'deepl' => $deepl]);
if ($deepl == 'Y') {
return $this->getTranslationWithDeepL($text, $sourceLanguage, $language, $html);
}
return $this->getTranslationWithGoogle($text, $sourceLanguage, $language);
}
public function getTranslationMulti(array $textMulti, string $sourceLanguage, string $language): array
{
foreach ($textMulti as $key => $text) {
if (empty($text)) {
continue;
}
$translation = $this->getTranslation($text, $sourceLanguage, $language);
if (empty($translation['error'])) {
$textMulti[$key] = $translation['translatedText'];
} else {
unset($textMulti[$key]);
}
}
return $textMulti;
}
private function getTranslationWithGoogle(string $text, string $sourceLanguage, string $language): array
{
// Google Translate nezná de-AT nebo de-CH, zná jen de-DE
// potřebuju to teda, aby se použila němčina - jinak to nefunguje vubec
$langAlias = [
'at' => 'de',
'ch' => 'de',
];
foreach ([&$sourceLanguage, &$language] as &$lang) {
if (isset($langAlias[$lang])) {
$lang = $langAlias[$lang];
}
}
$baseUrl = 'https://www.googleapis.com/language/translate/v2?key='.self::$googleApiKey; // .'&q='.rawurlencode($text).'&source='.$sourceLanguage.'&target='.$language
$textChunks = $this->splitTextIntoChunks($text);
$translatedChunks = [];
foreach ($textChunks as $chunk) {
$url = $baseUrl.'&q='.rawurlencode($chunk).'&source='.$sourceLanguage.'&target='.$language;
$handle = curl_init($url);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($handle);
$responseData = json_decode($response ?: '{}', true);
if (isset($responseData['error']) || curl_error($handle)) {
curl_close($handle);
return [
'error' => curl_error($handle) ?: ($responseData['error'] ?? 'Unknown error'),
'translatedText' => '',
];
}
$translatedChunks[] = html_entity_decode($responseData['data']['translations'][0]['translatedText'] ?? '', ENT_QUOTES);
curl_close($handle);
}
// Combine all translated chunks into a single result
$translatedText = implode(' ', $translatedChunks);
// Replace malformed sequences
$translatedText = str_replace(
['> , ', '> .'],
['>, ', '>.'],
$translatedText
);
return [
'error' => '',
'translatedText' => $translatedText,
];
}
private function getTranslationWithDeepL(string $text, string $sourceLanguage, string $language, bool $html = false): array
{
$responseData = ['translatedText' => ''];
try {
$translator = new Translator(self::$deepLAuthKey);
$language = $this->deeplLanguageMapping($language);
$sourceLanguage = $this->deeplLanguageMapping($sourceLanguage, true);
$options = [];
if ($html) {
$options['tag_handling'] = 'html';
$options['non_splitting_tags'] = ['strong', 'em', 'b', 'i'];
}
$result = $translator->translateText($text, $sourceLanguage, $language, $options);
if (!empty($result->text)) {
$responseData['translatedText'] = $result->text;
}
} catch (DeepLException $exception) {
$responseData['error'] = $exception->getMessage();
}
return $responseData;
}
protected function deeplLanguageMapping(string $language, bool $source = false): string
{
switch ($language) {
case 'en':
// if source language is 'en' => keep it 'en' otherwise use 'en-US'
return $source ? 'en' : 'en-US';
case 'at':
return 'de';
}
return $language;
}
protected function splitTextIntoChunks(string $text): array
{
$chunks = [];
$currentChunk = '';
// Split text into words to preserve boundaries
$words = preg_split('/(\s+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($words as $word) {
// Add word to the current chunk if it fits
if (mb_strlen($currentChunk.$word) <= self::GOOGLE_CHUNK) {
$currentChunk .= $word;
} else {
// Save the current chunk and start a new one
$chunks[] = trim($currentChunk);
$currentChunk = $word;
}
}
// Add the remaining chunk
if (!empty($currentChunk)) {
$chunks[] = trim($currentChunk);
}
return $chunks;
}
}