Files
kupshop/bundles/KupShop/I18nBundle/Resources/script/AutoTranslateScript.php
2025-08-02 16:30:27 +02:00

273 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace KupShop\I18nBundle\Resources\script;
use KupShop\AdminBundle\Util\Script\Script;
use KupShop\I18nBundle\Translations\BlocksTranslation;
use KupShop\I18nBundle\Translations\ITranslation;
use KupShop\I18nBundle\Util\TranslationEngine;
use KupShop\I18nBundle\Util\TranslationLocator;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use Query\Operator;
use Query\QueryBuilder;
use Query\Translation;
class AutoTranslateScript extends Script
{
protected static $name = 'Auto-translate translations using translator';
protected static $defaultParameters = [
// realRun: true = translate and save translations into DB
'realRun' => false,
'run_auto_translates' => false,
// translate from default language to specific languages
'languages' => ['sk'],
'po_files' => false,
];
private static array $translateDefinition = [];
public static function getDefaultParameters(): array
{
$translationLocator = ServiceContainer::getService(TranslationLocator::class);
$translations = $translationLocator->getTranslations();
$defaultParameters = self::$defaultParameters;
foreach ($translations as $translationClass) {
/** @var ITranslation $translation */
$translation = ServiceContainer::getService($translationClass);
$defaultParameters['autoTranslateClasses'][$translationClass] = array_keys($translation->getColumns());
}
self::$translateDefinition = $defaultParameters['autoTranslateClasses'] ?? [];
return $defaultParameters;
}
protected function run(array $arguments)
{
$languages = $arguments['languages'];
if (empty($languages)) {
$this->log('Argument "languages" cannot be empty!');
return;
}
$missingTranslations = [];
if (!empty($arguments['run_auto_translates'])) {
foreach ($arguments['autoTranslateClasses'] ?? [] as $className => $class) {
$missingTranslations[$className] = $this->getMissingTranslations($className, $languages);
}
}
$missingPoTranslations = [];
if (!empty($arguments['po_files'])) {
foreach ($languages as $language) {
$missingPoTranslations[$language] = $this->getMissingPoTranslations($language);
}
}
if (!($arguments['realRun'] ?? false)) {
foreach ($missingTranslations as $class => $translations) {
foreach ($translations as $lang => $items) {
$this->log("[{$lang}] {$class}: ".count($items));
}
}
$this->log('Statické texty: '.count($missingPoTranslations));
return;
}
ini_set('max_execution_time', 60 * 60 * 2); // 2 hours
$translator = ServiceContainer::getService(TranslationEngine::class);
$languageContext = Contexts::get(LanguageContext::class);
foreach ($missingTranslations as $class => $translations) {
$this->log("Translating {$class}...");
/** @var ITranslation $translation */
$translation = ServiceContainer::getService($class);
foreach ($translations as $lang => $items) {
$totalItems = count($items);
$processedItems = 0;
foreach ($items as $id => $item) {
$translatedValues = [];
foreach ($item as $column => $text) {
$translated = null;
if ($column == 'json_content' && $translation instanceof BlocksTranslation) {
$blocks = json_decode($text, true);
$text_blocks = [];
BlocksTranslation::getTextBlocks($blocks, $text_blocks);
foreach ($text_blocks as $key => $blockText) {
if (empty($blockText['settings']['html'])) {
continue;
}
try {
$translated = $translator->getTranslation(
$blockText['settings']['html'],
$languageContext->getDefaultId(),
$lang,
(bool) ($translation->getColumns()[$column]['richtext'] ?? false)
)['translatedText'] ?? null;
$blockText['settings']['html'] = $translated;
$text_blocks[$key] = $blockText;
} catch (\Throwable $e) {
}
}
BlocksTranslation::replaceRecursive($blocks, $text_blocks);
$translated = $blocks;
} else {
if (isLocalDevelopment()) {
$this->log('Překlad není povolen pro localDevelopment');
continue;
}
try {
$translated = $translator->getTranslation(
$text,
$languageContext->getDefaultId(),
$lang,
(bool) ($translation->getColumns()[$column]['richtext'] ?? false)
);
if (!empty($translated['error'])) {
$this->log($translated['error']);
continue;
}
$translated = $translated['translatedText'] ?? null;
} catch (\Throwable $e) {
}
}
// pokud mam preklad
if (!empty($translated)) {
$translatedValues[$column] = $translated;
}
if (!empty($translatedValues)) {
if ($column == 'json_content' && $translation instanceof BlocksTranslation) {
$translation->saveSingleObjectForce($lang, $id, $translatedValues, false);
} else {
$translation->saveSingleObject($lang, $id, $translatedValues);
}
}
}
$processedItems++;
if ($processedItems % 10 === 0) {
$this->log("{$processedItems}/{$totalItems}...");
}
}
}
}
foreach ($missingPoTranslations as $language => $translations) {
$totalItems = count($translations);
$processedItems = 0;
foreach ($translations as $translation) {
try {
$translated = $translator->getTranslation(
$translation['original'],
$languageContext->getDefaultId(),
$language,
false
);
if (!empty($translated['error'])) {
$this->log($translated['error']);
continue;
}
$translated = $translated['translatedText'] ?? null;
} catch (\Throwable $e) {
$this->log($e->getMessage());
}
if (!empty($translated)) {
sqlQueryBuilder()->update('translations_frontend')->directValues([
'translation' => $translated,
])->where(Operator::equals(['id' => $translation['id']]))->execute();
}
$processedItems++;
if ($processedItems % 10 === 0) {
$this->log("{$processedItems}/{$totalItems}...");
}
}
}
$this->log('Translations done');
}
private function getMissingTranslations(string $translationClass, array $languages): array
{
/** @var ITranslation $translation */
$translation = ServiceContainer::getService($translationClass);
if (!($columns = (static::$translateDefinition[$translationClass] ?? false))) {
$this->log('Žádné sloupce pro tabulku: '.$translationClass);
return [];
}
$missingTranslations = [];
$SQL = sqlQuery("SHOW TABLES LIKE '".$translation->getTableName()."'");
if (sqlNumRows($SQL) > 0) {
$qb = sqlQueryBuilder()
->select("{$translation->getTableAlias()}.id")
->from($translation->getTableName(), $translation->getTableAlias());
foreach ($columns as $column) {
$qb->addSelect("{$translation->getTableAlias()}.{$column}");
}
$qb->andWhere(
Translation::joinTranslations(
$languages,
$translation,
function (QueryBuilder $qb, $columnName, $translatedColumn, $langID) {
$qb->addSelect("{$translatedColumn} as translated_{$columnName}_{$langID}");
return false;
},
$columns
)
);
foreach ($qb->execute() as $item) {
foreach ($columns as $column) {
if (empty($item[$column] ?? '')) {
continue;
}
$originalText = trim($item[$column]);
// preskocim polozky, ktere nemaji text v default jazyce
if (empty($originalText)) {
continue;
}
foreach ($languages as $language) {
$translatedColumn = "translated_{$column}_{$language}";
if (empty($item[$translatedColumn])) {
$missingTranslations[$language][$item['id']][$column] = $originalText;
}
}
}
}
}
return $missingTranslations;
}
protected function getMissingPoTranslations($language)
{
return sqlQueryBuilder()->select('id, original, id_language')->from('translations_frontend')
->where(Operator::equals(['id_language' => $language]))
->andWhere(Operator::orX(
Operator::isNull('translation'),
Operator::equals(['translation' => ''])
))
->execute()->fetchAllAssociative();
}
}
return AutoTranslateScript::class;