diff --git a/composer.json b/composer.json index c81c2cc5df..bc79955492 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "stevenmaguire/oauth2-microsoft": "^2.2", "league/oauth2-github": "^3.1", "league/oauth2-facebook": "^2.2", + "translated/lara-sdk":"^1.0.1", "psr/log": "~1.0", "ext-curl": "*", "ext-pcntl": "*", diff --git a/composer.lock b/composer.lock index e96caf4887..4425b71b4e 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": "4879d1c070ae9d3f12f01f7c31efd172", + "content-hash": "c107c1842a725b53940e15bd728aa088", "packages": [ { "name": "aws/aws-crt-php", @@ -3978,6 +3978,50 @@ } ], "time": "2024-11-10T20:33:58+00:00" + }, + { + "name": "translated/lara-sdk", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/translated/lara-php.git", + "reference": "acd4a794550f25b65dc5dd33e75444d347a4c3b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/translated/lara-php/zipball/acd4a794550f25b65dc5dd33e75444d347a4c3b4", + "reference": "acd4a794550f25b65dc5dd33e75444d347a4c3b4", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lara\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Translated", + "email": "info@translated.com", + "homepage": "https://translated.com/" + } + ], + "description": "Official Lara SDK for PHP", + "homepage": "https://github.com/translated/lara-php", + "support": { + "issues": "https://github.com/translated/lara-php/issues", + "source": "https://github.com/translated/lara-php/tree/v1.0.1" + }, + "time": "2024-12-03T21:47:29+00:00" } ], "packages-dev": [ @@ -6319,7 +6363,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -6340,6 +6384,6 @@ "ext-iconv": "*", "ext-openssl": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/docker b/docker index bd604269bc..a67a0aaa32 160000 --- a/docker +++ b/docker @@ -1 +1 @@ -Subproject commit bd604269bc38a9e9715c1a38ae3b0e11f7ea472e +Subproject commit a67a0aaa32f2f4a241a28fee1e2957be9eaaa7e6 diff --git a/inc/INIT.php b/inc/INIT.php index 7f2206ebba..954b2c5924 100644 --- a/inc/INIT.php +++ b/inc/INIT.php @@ -155,7 +155,6 @@ class INIT { * @var int Interval in seconds */ public static $COPY_SOURCE_INTERVAL = 300; - public static $MAX_NUM_SEGMENTS = 500; /** * Default Matecat user agent string @@ -205,6 +204,11 @@ class INIT { */ public static $DEFAULT_TM_KEY = ''; + /** + * @var string The default MMT license is applied when Lara falls back for unsupported languages and the user does not add their personal MMT license. + */ + public static $DEFAULT_MMT_KEY = ''; + public static $ENABLED_BROWSERS = [ 'applewebkit', 'chrome', 'safari', 'edge', 'firefox' ]; /** diff --git a/lib/Controller/catController.php b/lib/Controller/catController.php index 9beda244eb..4586c710e9 100644 --- a/lib/Controller/catController.php +++ b/lib/Controller/catController.php @@ -186,16 +186,17 @@ public function doAction() { $active_mt_engine_array = []; if ( !empty( $active_mt_engine ) ) { + $engine_type = explode("\\", $active_mt_engine[ 0 ]->class_load); $active_mt_engine_array = [ "id" => $active_mt_engine[ 0 ]->id, "name" => $active_mt_engine[ 0 ]->name, "type" => $active_mt_engine[ 0 ]->type, "description" => $active_mt_engine[ 0 ]->description, - 'engine_type' => ( $active_mt_engine[ 0 ]->class_load === 'MyMemory' ? 'MMTLite' : $active_mt_engine[ 0 ]->class_load ), + 'engine_type' => ( $active_mt_engine[ 0 ]->class_load === 'MyMemory' ? 'MMTLite' : array_pop($engine_type) ), ]; } - $this->template->active_engine = Utils::escapeJsonEncode( $active_mt_engine_array ); + $this->template->active_engine = $active_mt_engine_array; /* * array_unique cast EnginesModel_EngineStruct to string @@ -380,7 +381,6 @@ public function setTemplateVars() { $this->template->mt_engines = $this->translation_engines; $this->template->translation_engines_intento_providers = Intento::getProviderList(); - $this->template->translation_engines_intento_prov_json = Utils::escapeJsonEncode( Intento::getProviderList() ); $this->template->not_empty_default_tm_key = !empty( INIT::$DEFAULT_TM_KEY ); @@ -412,7 +412,6 @@ public function setTemplateVars() { $this->template->maxTMXFileSize = INIT::$MAX_UPLOAD_TMX_FILE_SIZE; $this->template->tagLockCustomizable = ( INIT::$UNLOCKABLE_TAGS == true ) ? true : false; - $this->template->maxNumSegments = INIT::$MAX_NUM_SEGMENTS; $this->template->copySourceInterval = INIT::$COPY_SOURCE_INTERVAL; /* diff --git a/lib/Controller/engineController.php b/lib/Controller/engineController.php index dea4c703a6..0bdad93399 100755 --- a/lib/Controller/engineController.php +++ b/lib/Controller/engineController.php @@ -1,6 +1,10 @@ [ 'getTermList' ] // letsmt no longer requires this function. it's left as an example + // 'letsmt' => [ 'getTermList' ] // letsmt no longer requires this function. it's left as an example ]; public function __construct() { @@ -126,7 +130,7 @@ private function add() { $newEngineStruct->extra_parameters[ 'DeepL-Auth-Key' ] = $this->engineData[ 'client_id' ]; try { - DeepLValidator::validate($newEngineStruct); + DeepLValidator::validate( $newEngineStruct ); } catch ( Exception $e ) { $this->result[ 'errors' ][] = [ 'code' => $e->getCode(), 'message' => $e->getMessage() ]; @@ -143,11 +147,11 @@ private function add() { */ $newEngineStruct = EnginesModel_MicrosoftHubStruct::getStruct(); - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters[ 'client_id' ] = $this->engineData[ 'client_id' ]; - $newEngineStruct->extra_parameters[ 'category' ] = $this->engineData[ 'category' ]; + $newEngineStruct->name = $this->name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'client_id' ] = $this->engineData[ 'client_id' ]; + $newEngineStruct->extra_parameters[ 'category' ] = $this->engineData[ 'category' ]; break; case strtolower( Constants_Engines::APERTIUM ): @@ -221,28 +225,42 @@ private function add() { break; - case strtolower(Constants_Engines::INTENTO): + case strtolower( Constants_Engines::INTENTO ): /** * Create a record of type Intento */ - $newEngineStruct = EnginesModel_IntentoStruct::getStruct(); - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters['apikey'] = $this->engineData['secret']; - $newEngineStruct->extra_parameters['provider'] = $this->engineData['provider']; - $newEngineStruct->extra_parameters['providerkey'] = $this->engineData['providerkey']; - $newEngineStruct->extra_parameters['providercategory'] = $this->engineData['providercategory']; + $newEngineStruct = EnginesModel_IntentoStruct::getStruct(); + $newEngineStruct->name = $this->name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'apikey' ] = $this->engineData[ 'secret' ]; + $newEngineStruct->extra_parameters[ 'provider' ] = $this->engineData[ 'provider' ]; + $newEngineStruct->extra_parameters[ 'providerkey' ] = $this->engineData[ 'providerkey' ]; + $newEngineStruct->extra_parameters[ 'providercategory' ] = $this->engineData[ 'providercategory' ]; + break; + + case strtolower( Constants_Engines::LARA ): + /** + * Create a record of type Lara + */ + $newEngineStruct = LaraStruct::getStruct(); + + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'Lara-AccessKeyId' ] = $this->engineData[ 'lara-access-key-id' ]; + $newEngineStruct->extra_parameters[ 'Lara-AccessKeySecret' ] = $this->engineData[ 'secret' ]; + $newEngineStruct->extra_parameters[ 'MMT-License' ] = $this->engineData[ 'mmt-license' ]; + break; default: // MMT $validEngine = $newEngineStruct = $this->featureSet->filter( 'buildNewEngineStruct', false, (object)[ - 'featureSet' => $this->featureSet, - 'providerName' => $this->provider, - 'logged_user' => $this->user, - 'engineData' => $this->engineData + 'featureSet' => $this->featureSet, + 'providerName' => $this->provider, + 'logged_user' => $this->user, + 'engineData' => $this->engineData ] ); break; @@ -254,14 +272,20 @@ private function add() { return; } - $engineList = $this->featureSet->filter( 'getAvailableEnginesListForUser', Constants_Engines::getAvailableEnginesList(), $this->user ); + $engineList = Constants_Engines::getAvailableEnginesList(); + $UserMetadataDao = new MetadataDao(); + $engineEnabled = $UserMetadataDao->get( $this->user->uid, $newEngineStruct->class_load ); + + if ( !empty( $engineEnabled ) ) { + unset( $engineList[ $newEngineStruct->class_load ] ); + } $engineDAO = new EnginesModel_EngineDAO( Database::obtain() ); $newCreatedDbRowStruct = null; if ( array_search( $newEngineStruct->class_load, $engineList ) ) { $newEngineStruct->active = true; - $newCreatedDbRowStruct = $engineDAO->create( $newEngineStruct ); + $newCreatedDbRowStruct = $engineDAO->create( $newEngineStruct ); $this->destroyUserEnginesCache(); } @@ -311,6 +335,26 @@ private function add() { return; } + } elseif ( $newEngineStruct instanceof LaraStruct ) { + + /** + * @var $newTestCreatedMT Lara + */ + $newTestCreatedMT = Engine::createTempInstance( $newCreatedDbRowStruct ); + + try { + $newTestCreatedMT->getAvailableLanguages(); + } catch ( LaraException $e ) { + $this->result[ 'errors' ][] = $e->getMessage(); + $engineDAO->delete( $newCreatedDbRowStruct ); + $this->destroyUserEnginesCache(); + + return; + } + + $UserMetadataDao = new MetadataDao(); + $UserMetadataDao->set( $this->user->uid, $newCreatedDbRowStruct->class_load, $newCreatedDbRowStruct->id ); + } else { try { @@ -325,11 +369,12 @@ private function add() { } + $engine_type = explode( "\\", $newCreatedDbRowStruct->class_load ); $this->result[ 'data' ][ 'id' ] = $newCreatedDbRowStruct->id; $this->result[ 'data' ][ 'name' ] = $newCreatedDbRowStruct->name; $this->result[ 'data' ][ 'description' ] = $newCreatedDbRowStruct->description; $this->result[ 'data' ][ 'type' ] = $newCreatedDbRowStruct->type; - $this->result[ 'data' ][ 'engine_type' ] = $newCreatedDbRowStruct->class_load; + $this->result[ 'data' ][ 'engine_type' ] = array_pop( $engine_type ); } /** @@ -359,7 +404,12 @@ private function disable() { return; } - $this->featureSet->run( 'postEngineDeletion', $result ); + $engine = Engine::createTempInstance( $result ); + + if ( $engine->isAdaptive() ) { + //retrieve OWNER Engine License + ( new MetadataDao() )->delete( $this->user->uid, $result->class_load ); // engine_id + } $this->result[ 'data' ][ 'id' ] = $result->id; diff --git a/lib/Controller/getContributionController.php b/lib/Controller/getContributionController.php index 89b5d287ed..a80bc6fdaf 100644 --- a/lib/Controller/getContributionController.php +++ b/lib/Controller/getContributionController.php @@ -24,27 +24,37 @@ class getContributionController extends ajaxController { private $__postInput; private $cross_language; + /** + * @var ?array + */ + private ?array $context_list_before; + /** + * @var ?array + */ + private ?array $context_list_after; public function __construct() { parent::__construct(); $filterArgs = [ - 'id_segment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'num_results' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'text' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'id_translator' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'current_password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'is_concordance' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'from_target' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'context_before' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'context_after' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'id_before' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_after' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_client' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'cross_language' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FORCE_ARRAY ] + 'id_segment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], + 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], + 'num_results' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], + 'text' => [ 'filter' => FILTER_UNSAFE_RAW ], + 'id_translator' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], + 'password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], + 'current_password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], + 'is_concordance' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], + 'from_target' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], + 'context_before' => [ 'filter' => FILTER_UNSAFE_RAW ], + 'context_after' => [ 'filter' => FILTER_UNSAFE_RAW ], + 'id_before' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], + 'id_after' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], + 'id_client' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], + 'cross_language' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FORCE_ARRAY ], + 'context_list_before' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_NO_ENCODE_QUOTES ], + 'context_list_after' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_NO_ENCODE_QUOTES ], ]; $this->__postInput = filter_input_array( INPUT_POST, $filterArgs ); @@ -57,16 +67,18 @@ public function __construct() { $this->id_before = $this->__postInput[ 'id_before' ]; $this->id_after = $this->__postInput[ 'id_after' ]; - $this->id_job = $this->__postInput[ 'id_job' ]; - $this->num_results = $this->__postInput[ 'num_results' ]; - $this->text = trim( $this->__postInput[ 'text' ] ); - $this->id_translator = $this->__postInput[ 'id_translator' ]; - $this->concordance_search = $this->__postInput[ 'is_concordance' ]; - $this->switch_languages = $this->__postInput[ 'from_target' ]; - $this->password = $this->__postInput[ 'password' ]; - $this->received_password = $this->__postInput[ 'current_password' ]; - $this->id_client = $this->__postInput[ 'id_client' ]; - $this->cross_language = $this->__postInput[ 'cross_language' ]; + $this->id_job = $this->__postInput[ 'id_job' ]; + $this->num_results = $this->__postInput[ 'num_results' ]; + $this->text = trim( $this->__postInput[ 'text' ] ); + $this->id_translator = $this->__postInput[ 'id_translator' ]; + $this->concordance_search = $this->__postInput[ 'is_concordance' ]; + $this->switch_languages = $this->__postInput[ 'from_target' ]; + $this->password = $this->__postInput[ 'password' ]; + $this->received_password = $this->__postInput[ 'current_password' ]; + $this->id_client = $this->__postInput[ 'id_client' ]; + $this->cross_language = $this->__postInput[ 'cross_language' ]; + $this->context_list_after = json_decode( $this->__postInput[ 'context_list_after' ], true ); + $this->context_list_before = json_decode( $this->__postInput[ 'context_list_before' ], true ); if ( $this->id_translator == 'unknown_translator' ) { $this->id_translator = ""; @@ -119,20 +131,24 @@ public function doAction() { $this->_getContexts( $jobStruct->source, $jobStruct->target ); } - $file = (new FilesPartsDao())->getBySegmentId($this->id_segment); - $owner = (new Users_UserDao())->getProjectOwner( $this->id_job ); - - $contributionRequest = new ContributionRequestStruct(); - $contributionRequest->id_file = $file->id_file; - $contributionRequest->id_job = $this->id_job; - $contributionRequest->password = $this->received_password; - $contributionRequest->user = $owner; - $contributionRequest->dataRefMap = $dataRefMap; - $contributionRequest->contexts = [ - 'context_before' => $this->context_before, - 'segment' => $this->text, - 'context_after' => $this->context_after + $file = ( new FilesPartsDao() )->getBySegmentId( $this->id_segment ); + $owner = ( new Users_UserDao() )->getProjectOwner( $this->id_job ); + + $contributionRequest = new ContributionRequestStruct(); + $contributionRequest->id_file = $file->id_file; + $contributionRequest->id_job = $this->id_job; + $contributionRequest->password = $this->received_password; + $contributionRequest->user = $owner; + $contributionRequest->dataRefMap = $dataRefMap; + $contributionRequest->contexts = [ + 'context_before' => $this->context_before, + 'segment' => $this->text, + 'context_after' => $this->context_after ]; + + $contributionRequest->context_list_before = $this->context_list_before; + $contributionRequest->context_list_after = $this->context_list_after; + $contributionRequest->jobStruct = $jobStruct; $contributionRequest->projectStruct = $projectStruct; $contributionRequest->segmentId = $this->id_segment; diff --git a/lib/Controller/loadTMXController.php b/lib/Controller/loadTMXController.php index 4b038729a6..2affeb325a 100644 --- a/lib/Controller/loadTMXController.php +++ b/lib/Controller/loadTMXController.php @@ -126,11 +126,9 @@ public function doAction() { $fileInfo->name ); - $this->TMService->addTmxInMyMemory( $file ); + $this->TMService->addTmxInMyMemory( $file, $this->user ); $uuids[] = [ "uuid" => $file->getUuid(), "name" => $file->getName() ]; - $this->featureSet->run( 'postPushTMX', $file, $this->user ); - /* * We update the KeyRing only if this is NOT the Default MyMemory Key * diff --git a/lib/Controller/setTranslationController.php b/lib/Controller/setTranslationController.php index 8d78c6cf48..b48a13fc5b 100644 --- a/lib/Controller/setTranslationController.php +++ b/lib/Controller/setTranslationController.php @@ -836,7 +836,7 @@ private function updateJobPEE( array $old_translation, array $new_translation ) 'id' => $this->id_job, 'password' => $this->password ] ); - } else { + } elseif( $tte != 0 ) { Jobs_JobDao::updateFields( [ 'total_time_to_edit' => $tte ], [ @@ -941,7 +941,6 @@ private function evalSetContribution( $_Translation, $old_translation ) { Set::contribution( $contributionStruct ); if ( $contributionStruct->id_mt > 1 ) { - $contributionStruct = $this->featureSet->filter( 'filterSetContributionMT', null, $contributionStruct, $this->project ); Set::contributionMT( $contributionStruct ); } diff --git a/lib/Model/EnginesModel/EngineStruct.php b/lib/Model/EnginesModel/EngineStruct.php index 378a3723ad..3e486fc313 100644 --- a/lib/Model/EnginesModel/EngineStruct.php +++ b/lib/Model/EnginesModel/EngineStruct.php @@ -69,7 +69,7 @@ class EnginesModel_EngineStruct /** - * @var array + * @var array|string */ public $extra_parameters; @@ -186,7 +186,7 @@ public function getExtraParamsAsArray() { return $this->extra_parameters; } - if ( empty( $this->extra_parameters ) or $this->extra_parameters === null ) { + if ( empty( $this->extra_parameters ) ) { return []; } diff --git a/lib/Model/EnginesModel/LaraStruct.php b/lib/Model/EnginesModel/LaraStruct.php new file mode 100644 index 0000000000..f3a7d495ce --- /dev/null +++ b/lib/Model/EnginesModel/LaraStruct.php @@ -0,0 +1,93 @@ + "memories/content", + "user_update_activate" => "memories/connect", + ]; + + /** + * @var string + */ + public $class_load = Lara::class; + + + /** + * @var array + */ + public $extra_parameters = [ + 'Lara-AccessKeyId' => "", + 'Lara-AccessKeySecret' => "", + 'MMT-License' => "" + ]; + + /** + * @var int + */ + public $google_api_compliant_version = 2; + + /** + * @var int + */ + public $penalty = 14; + + /** + * An empty struct + * @return LaraStruct + */ + public static function getStruct(): LaraStruct { + return new LaraStruct(); + } +} \ No newline at end of file diff --git a/lib/Model/Users/MetadataDao.php b/lib/Model/Users/MetadataDao.php index 317941c612..678a5aa1ce 100644 --- a/lib/Model/Users/MetadataDao.php +++ b/lib/Model/Users/MetadataDao.php @@ -4,6 +4,7 @@ use Database; use PDO; +use ReflectionException; class MetadataDao extends \DataAccess_AbstractDao { @@ -57,15 +58,16 @@ public function getAllByUid( $uid ) { * @param $key * * @return MetadataStruct + * @throws ReflectionException */ - public function get( $uid, $key ) { + public function get( $uid, $key ): ?MetadataStruct { $stmt = $this->_getStatementForQuery( self::_query_metadata_by_uid_key ); + /** @var $result MetadataStruct */ $result = $this->_fetchObject( $stmt, new MetadataStruct(), [ 'uid' => $uid, 'key' => $key ] ); - - return @$result[ 0 ]; + return $result[ 0 ] ?? null; } public function destroyCacheKey( $uid, $key ){ diff --git a/lib/Plugins/Features/Mmt.php b/lib/Plugins/Features/Mmt.php index 1863d0b00d..4844a6ae8f 100644 --- a/lib/Plugins/Features/Mmt.php +++ b/lib/Plugins/Features/Mmt.php @@ -61,36 +61,6 @@ public static function bootstrapCompleted() { Constants_Engines::setInEnginesList( Constants_Engines::MMT ); } - /** - * Called in @param $enginesList - * - * @param Users_UserStruct $userStruct - * - * @return mixed - * @throws Exception - * @see engineController::add() - * - * Only one MMT engine per user can be registered - * - */ - public static function getAvailableEnginesListForUser( $enginesList, Users_UserStruct $userStruct ) { - - $UserMetadataDao = new MetadataDao(); - $engineEnabled = $UserMetadataDao->get( $userStruct->uid, self::FEATURE_CODE ); - - if ( !empty( $engineEnabled ) ) { - - $engine = Engine::getInstance($engineEnabled->value); - $engineRecord = $engine->getEngineRecord(); - - if($engineRecord->active == 1){ - unset( $enginesList[ Constants_Engines::MMT ] ); // remove the engine from the list of available engines like it was disabled, so it will not be created - } - } - - return $enginesList; - } - /** * Called in @param EnginesModel_EngineStruct $newCreatedDbRowStruct * @@ -110,28 +80,28 @@ public static function postEngineCreation( EnginesModel_EngineStruct $newCreated /** @var Engines_MMT $newTestCreatedMT */ try { $newTestCreatedMT = Engine::createTempInstance( $newCreatedDbRowStruct ); - } catch (Exception $exception){ - throw new Exception("MMT license not valid"); + } catch ( Exception $exception ) { + throw new Exception( "MMT license not valid" ); } // Check account try { $checkAccount = $newTestCreatedMT->checkAccount(); - if(!isset($checkAccount['billingPeriod']['planForCatTool'])){ - throw new Exception("MMT license not valid"); + if ( !isset( $checkAccount[ 'billingPeriod' ][ 'planForCatTool' ] ) ) { + throw new Exception( "MMT license not valid" ); } - $planForCatTool = $checkAccount['billingPeriod']['planForCatTool']; + $planForCatTool = $checkAccount[ 'billingPeriod' ][ 'planForCatTool' ]; - if($planForCatTool === false){ - throw new Exception("The ModernMT license you entered cannot be used inside CAT tools. Please subscribe to a suitable license to start using the ModernMT plugin."); + if ( $planForCatTool === false ) { + throw new Exception( "The ModernMT license you entered cannot be used inside CAT tools. Please subscribe to a suitable license to start using the ModernMT plugin." ); } - } catch ( Exception $e ){ + } catch ( Exception $e ) { ( new EnginesModel_EngineDAO( Database::obtain() ) )->delete( $newCreatedDbRowStruct ); - throw new Exception($e->getMessage(), $e->getCode()); + throw new Exception( $e->getMessage(), $e->getCode() ); } try { @@ -173,31 +143,13 @@ public function engineCreationFailed( $errorObject, $class_load ) { } /** - * Called in @param EnginesModel_EngineStruct $engineStruct - * @throws Exception - * @see engineController::disable() - */ - public static function postEngineDeletion( EnginesModel_EngineStruct $engineStruct ) { - - $UserMetadataDao = new MetadataDao(); - $engineEnabled = $UserMetadataDao->setCacheTTL( 60 * 60 * 24 * 30 )->get( $engineStruct->uid, self::FEATURE_CODE ); - - if ( $engineStruct->class_load == Constants_Engines::MMT ) { - - if ( !empty( $engineEnabled ) && $engineEnabled->value == $engineStruct->id /* redundant */ ) { - $UserMetadataDao->delete( $engineStruct->uid, self::FEATURE_CODE ); // delete the engine from user - } - - } - } - - /** - * Called in @param $config + * Called in * + * @param array $config * @param Engines_AbstractEngine $engine * @param Jobs_JobStruct $jobStruct * - * @return mixed + * @return array * @throws Exception * @see getContributionController::doAction() * @@ -243,13 +195,13 @@ public static function analysisBeforeMTGetContribution( $config, Engines_Abstrac $config[ 'mt_context' ] = $mt_context->value; } - if( $mt_evaluation ){ + if ( $mt_evaluation ) { $config[ 'include_score' ] = true; } $config[ 'secret_key' ] = self::getG2FallbackSecretKey(); $config[ 'priority' ] = 'background'; - $config[ 'keys' ] = @$config[ 'id_user' ]; + $config[ 'keys' ] = $config[ 'id_user' ] ?? []; } @@ -319,12 +271,12 @@ public function filterCreateProjectFeatures( $projectFeatures, $controller ) { if ( $engine instanceof Engines_MMT ) { /** * @var $availableLangs - * - * { + * + * { * "en":["it","zh-TW"], * "de":["en"] - * } - * + * } + * */ $availableLangs = $engine->getAvailableLanguages(); $target_language_list = explode( ",", $controller->postInput[ 'target_lang' ] ); @@ -479,110 +431,6 @@ public static function beforeSendSegmentsToTheQueue( array $segments, array $pro } - /** - * - * Called in @param $response - * - * @param ContributionSetStruct $contributionStruct - * @param Projects_ProjectStruct $projectStruct - * - * @return ContributionSetStruct|null - * @see \setTranslationController::evalSetContribution() - * - */ - public function filterSetContributionMT( $response, ContributionSetStruct $contributionStruct, Projects_ProjectStruct $projectStruct ) { - - /** - * When a project is created, it's features and used plugins are stored in project_metadata, - * When MMT is disabled at global level, old projects will have this feature enabled in meta_data, but the plugin is not Bootstrapped with the hook @see Mmt::bootstrapCompleted() - * - * So, the MMT engine is not in the list of available plugins, check and exclude if the Plugin is not enables at global level - */ - if ( !array_key_exists( Constants_Engines::MMT, Constants_Engines::getAvailableEnginesList() ) ) { - return $response; - } - - //Project is not anonymous - if ( !$projectStruct->isAnonymous() ) { - - try { - - $features = FeatureSet::splitString( $projectStruct->getMetadataValue( Projects_MetadataDao::FEATURES_KEY ) ); - - if ( in_array( self::FEATURE_CODE, $features ) ) { - $response = $contributionStruct; - } else { - $response = null; - } - - } catch ( Exception $e ) { - //DO Nothing - } - - } - - return $response; - - } - - /** - * - * @param TMSFile $file - * @param $user - * - * Called in @see \ProjectManager::_pushTMXToMyMemory() - * Called in @see \loadTMXController::doAction() - * - */ - public function postPushTMX( TMSFile $file, $user ) { - - //Project is not anonymous - if ( !empty( $user ) ) { - - $uStruct = $user; - if ( !$uStruct instanceof Users_UserStruct ) { - $uStruct = ( new Users_UserDao() )->setCacheTTL( 60 * 60 * 24 * 30 )->getByEmail( $user ); - } - - //retrieve OWNER MMT License - $ownerMmtEngineMetaData = ( new MetadataDao() )->setCacheTTL( 60 * 60 * 24 * 30 )->get( $uStruct->uid, self::FEATURE_CODE ); // engine_id - try { - - if ( !empty( $ownerMmtEngineMetaData ) ) { - - /** - * @var Engines_MMT $MMTEngine - */ - $MMTEngine = Engine::getInstance( $ownerMmtEngineMetaData->value ); - - // - // ============================================== - // Call MMT only if the tmx is already imported - // over an existing key in MMT - // ============================================== - // - - $associatedMemories = $MMTEngine->getAllMemories(); - foreach ( $associatedMemories as $memory ) { - - if ( 'x_mm-' . trim( $file->getTmKey() ) === $memory[ 'externalId' ] ) { - $fileName = AbstractFilesStorage::pathinfo_fix( $file->getFilePath(), PATHINFO_FILENAME ); - $result = $MMTEngine->import( $file->getFilePath(), $file->getTmKey(), $fileName ); - - if ( $result->responseStatus >= 400 ) { - throw new Exception( $result->error->message ); - } - } - } - } - - } catch ( Exception $e ) { - Log::doJsonLog( $e->getMessage() ); - } - } - - } - /** * Called in @param $isValid * diff --git a/lib/Utils/Analysis/Workers/TMAnalysisWorker.php b/lib/Utils/Analysis/Workers/TMAnalysisWorker.php index e32ef0e3f6..72a0b38fe2 100644 --- a/lib/Utils/Analysis/Workers/TMAnalysisWorker.php +++ b/lib/Utils/Analysis/Workers/TMAnalysisWorker.php @@ -274,7 +274,7 @@ protected function _updateRecord( QueueElement $queueElement ) { $this->_incrementAnalyzedCount( $queueElement->params->pid, $eq_words, $standard_words ); $this->_decSegmentsToAnalyzeOfWaitingProjects( $queueElement->params->pid ); $this->_tryToCloseProject( $queueElement->params ); - + } /** @@ -640,6 +640,9 @@ protected function _getMT( Engines_AbstractEngine $mtEngine, $_config, QueueElem $config = $mtEngine->getConfigStruct(); $config = array_merge( $config, $_config ); + // set for lara engine in case, this is needed to catch all owner keys + $config[ 'all_job_tm_keys' ] = $queueElement->params->tm_keys; + //if a callback is not set only the first argument is returned, get the config params from the callback $config = $this->featureSet->filter( 'analysisBeforeMTGetContribution', $config, $mtEngine, $queueElement, $mt_evaluation ); diff --git a/lib/Utils/AsyncTasks/Workers/GetContributionWorker.php b/lib/Utils/AsyncTasks/Workers/GetContributionWorker.php index 069f9c38b8..9a524ea697 100644 --- a/lib/Utils/AsyncTasks/Workers/GetContributionWorker.php +++ b/lib/Utils/AsyncTasks/Workers/GetContributionWorker.php @@ -9,12 +9,15 @@ namespace AsyncTasks\Workers; +use API\Commons\Exceptions\AuthenticationError; use Constants\Ices; use Constants_TranslationStatus; use Contribution\ContributionRequestStruct; use Engines_DeepL; use Engines_MMT; use Exception; +use Exceptions\NotFoundException; +use Exceptions\ValidationError; use FeatureSet; use INIT; use Jobs\MetadataDao; @@ -28,6 +31,7 @@ use TaskRunner\Commons\AbstractWorker; use TaskRunner\Commons\QueueElement; use TaskRunner\Exceptions\EndQueueException; +use TaskRunner\Exceptions\ReQueueException; use TmKeyManagement_TmKeyManagement; use Translations_SegmentTranslationDao; use Users_UserStruct; @@ -188,7 +192,7 @@ protected function _publishPayload( array $content, ContributionRequestStruct $c protected function _extractAvailableKeysForUser( ContributionRequestStruct $contributionStruct ) { //find all the job's TMs with write grants and make a contribution to them - $tm_keys = TmKeyManagement_TmKeyManagement::getJobTmKeys( $contributionStruct->getJobStruct()->tm_keys, 'r', 'tm', $contributionStruct->user->uid, $contributionStruct->userRole ); + $tm_keys = TmKeyManagement_TmKeyManagement::getJobTmKeys( $contributionStruct->getJobStruct()->tm_keys, 'r', 'tm', $contributionStruct->getUser()->uid, $contributionStruct->userRole ); $keyList = []; if ( !empty( $tm_keys ) ) { @@ -396,20 +400,22 @@ protected function tokenizeSourceSearch( $text ) { /** * @param ContributionRequestStruct $contributionStruct * @param Jobs_JobStruct $jobStruct - * @param $targetLang - * @param $featureSet + * @param string $targetLang + * @param FeatureSet $featureSet * @param bool $isCrossLang * * @return array + * @throws EndQueueException + * @throws ReQueueException * @throws Exception */ - protected function _getMatches( ContributionRequestStruct $contributionStruct, $jobStruct, $targetLang, $featureSet, $isCrossLang = false ) { + protected function _getMatches( ContributionRequestStruct $contributionStruct, Jobs_JobStruct $jobStruct, string $targetLang, FeatureSet $featureSet, bool $isCrossLang = false ): array { $_config = []; $_config[ 'segment' ] = $contributionStruct->getContexts()->segment; $_config[ 'source' ] = $jobStruct->source; $_config[ 'target' ] = $targetLang; - $_config[ 'uid' ] = ( $contributionStruct->user !== null ? $contributionStruct->user->uid : 0 ); + $_config[ 'uid' ] = $contributionStruct->getUser()->uid ?? 0; $_config[ 'email' ] = INIT::$MYMEMORY_API_KEY; @@ -500,7 +506,11 @@ protected function _getMatches( ContributionRequestStruct $contributionStruct, $ $mt_result = []; - if ( $jobStruct->id_mt_engine > 1 /* Request MT Directly */ && !$contributionStruct->concordanceSearch ) { + if ( + $jobStruct->id_mt_engine > 1 /* Request MT Directly */ && + !$contributionStruct->concordanceSearch && + !$isCrossLang + ) { if ( empty( $tms_match ) || (int)str_replace( "%", "", $tms_match[ 0 ][ 'match' ] ) < 100 ) { @@ -510,51 +520,18 @@ protected function _getMatches( ContributionRequestStruct $contributionStruct, $ //if a callback is not set only the first argument is returned, get the config params from the callback $config = $featureSet->filter( 'beforeGetContribution', $config, $mt_engine, $jobStruct ); - $config[ 'segment' ] = $contributionStruct->getContexts()->segment; - $config[ 'source' ] = $jobStruct->source; - $config[ 'target' ] = $jobStruct->target; - $config[ 'email' ] = INIT::$MYMEMORY_API_KEY; - $config[ 'segid' ] = $contributionStruct->segmentId; - $config[ 'job_id' ] = $jobStruct->id; - $config[ 'job_password' ] = $jobStruct->password; - $config[ 'session' ] = $contributionStruct->getSessionId(); - - if ( $mt_engine instanceof Engines_MMT ) { - $metadataDao = new Projects_MetadataDao(); - $metadata = $metadataDao->setCacheTTL( 86400 )->get( $contributionStruct->getProjectStruct()->id, 'mmt_glossaries' ); - - if ( $metadata !== null ) { - $metadata = html_entity_decode( $metadata->value ); - $mmtGlossariesArray = json_decode( $metadata, true ); - - $config[ 'glossaries' ] = implode( ",", $mmtGlossariesArray[ 'glossaries' ] ); - $config[ 'ignore_glossary_case' ] = $mmtGlossariesArray[ 'ignore_glossary_case' ]; - } - } - - // glossaries (only for DeepL) - if ( $mt_engine instanceof Engines_DeepL ) { - - $extraParams = $mt_engine->getEngineRecord()->extra_parameters; - - if ( !isset( $extraParams[ 'DeepL-Auth-Key' ] ) ) { - throw new Exception( "DeepL API key not set" ); - } - - $metadataDao = new Projects_MetadataDao(); - $deepLFormality = $metadataDao->get( $contributionStruct->getProjectStruct()->id, 'deepl_formality', 86400 ); - $deepLIdGlossary = $metadataDao->get( $contributionStruct->getProjectStruct()->id, 'deepl_id_glossary', 86400 ); - - if ( $deepLFormality !== null ) { - $config[ 'formality' ] = $deepLFormality->value; - } - - if ( $deepLIdGlossary !== null ) { - $config[ 'idGlossary' ] = $deepLIdGlossary->value; - } - - $mt_engine->setApiKey( $extraParams[ 'DeepL-Auth-Key' ] ); - } + $config[ 'segment' ] = $contributionStruct->getContexts()->segment; + $config[ 'source' ] = $jobStruct->source; + $config[ 'target' ] = $jobStruct->target; + $config[ 'email' ] = INIT::$MYMEMORY_API_KEY; + $config[ 'segid' ] = $contributionStruct->segmentId; + $config[ 'job_id' ] = $jobStruct->id; + $config[ 'job_password' ] = $jobStruct->password; + $config[ 'session' ] = $contributionStruct->getSessionId(); + $config[ 'all_job_tm_keys' ] = $jobStruct->tm_keys; + $config[ 'project_id' ] = $contributionStruct->getProjectStruct()->id; + $config[ 'context_list_before' ] = $contributionStruct->context_list_before; + $config[ 'context_list_after' ] = $contributionStruct->context_list_after; $mt_result = $mt_engine->get( $config ); } diff --git a/lib/Utils/AsyncTasks/Workers/SetContributionMTWorker.php b/lib/Utils/AsyncTasks/Workers/SetContributionMTWorker.php index 10b23c585f..a3ac26a371 100644 --- a/lib/Utils/AsyncTasks/Workers/SetContributionMTWorker.php +++ b/lib/Utils/AsyncTasks/Workers/SetContributionMTWorker.php @@ -51,7 +51,7 @@ protected function _set( array $config, ContributionSetStruct $contributionStruc $config[ 'translation' ] = $contributionStruct->translation; $config[ 'session' ] = $contributionStruct->getSessionId(); $config[ 'uid' ] = $contributionStruct->uid; - $config[ 'set_mt' ] = ( $jobStruct->id_mt_engine != 1 ) ? false : true; + $config[ 'set_mt' ] = $jobStruct->id_mt_engine == 1; // negate, if mt is 1, then is mymemory, and the flag set_mt must be set to true // set the contribution for every key in the job belonging to the user $res = $this->_engine->set( $config ); @@ -70,11 +70,13 @@ protected function _set( array $config, ContributionSetStruct $contributionStruc */ protected function _update( array $config, ContributionSetStruct $contributionStruct, $id_mt_engine = 0 ) { - $config[ 'segment' ] = $contributionStruct->segment; - $config[ 'translation' ] = $contributionStruct->translation; - $config[ 'tuid' ] = $contributionStruct->id_job . ":" . $contributionStruct->id_segment; - $config[ 'session' ] = $contributionStruct->getSessionId(); - $config[ 'set_mt' ] = ( $id_mt_engine != 1 ) ? false : true; + $config[ 'segment' ] = $contributionStruct->segment; + $config[ 'translation' ] = $contributionStruct->translation; + $config[ 'tuid' ] = $contributionStruct->id_job . ":" . $contributionStruct->id_segment; + $config[ 'session' ] = $contributionStruct->getSessionId(); + $config[ 'set_mt' ] = $id_mt_engine == 1; // negate, if mt is 1, then is mymemory, and the flag set_mt must be set to true + $config[ 'context_before' ] = $contributionStruct->context_before; + $config[ 'context_after' ] = $contributionStruct->context_after; // set the contribution for every key in the job belonging to the user $res = $this->_engine->update( $config ); diff --git a/lib/Utils/AsyncTasks/Workers/SetContributionWorker.php b/lib/Utils/AsyncTasks/Workers/SetContributionWorker.php index 92a3ba4ba2..3aeeb2af82 100644 --- a/lib/Utils/AsyncTasks/Workers/SetContributionWorker.php +++ b/lib/Utils/AsyncTasks/Workers/SetContributionWorker.php @@ -83,6 +83,13 @@ protected function _execContribution( ContributionSetStruct $contributionStruct $this->_loadEngine( $jobStruct ); + /** + * @see Engines_AbstractEngine::$_isAdaptive + */ + if( !$this->_engine->isAdaptive() ){ + return; + } + $config = $this->_engine->getConfigStruct(); $config[ 'source' ] = $jobStruct->source; $config[ 'target' ] = $jobStruct->target; diff --git a/lib/Utils/Constants/Engines.php b/lib/Utils/Constants/Engines.php index 3a1850bd48..7a4fe9979b 100644 --- a/lib/Utils/Constants/Engines.php +++ b/lib/Utils/Constants/Engines.php @@ -1,5 +1,7 @@ self::INTENTO, self::MMT => self::MMT, self::DEEPL => self::DEEPL, + Lara::class => Lara::class, // new namespaced engine classes must be loaded by fully qualified class name ]; /** diff --git a/lib/Utils/Contribution/ContributionRequestStruct.php b/lib/Utils/Contribution/ContributionRequestStruct.php index 4f7218dd4a..5b61bc7e31 100644 --- a/lib/Utils/Contribution/ContributionRequestStruct.php +++ b/lib/Utils/Contribution/ContributionRequestStruct.php @@ -34,6 +34,9 @@ class ContributionRequestStruct extends ShapelessConcreteStruct implements DataA 'context_after' => null ]; + public ?array $context_list_before = null; + public ?array $context_list_after = null; + /** * @var string */ diff --git a/lib/Utils/Engine.php b/lib/Utils/Engine.php index 679f520c7a..5bf36503c7 100644 --- a/lib/Utils/Engine.php +++ b/lib/Utils/Engine.php @@ -37,8 +37,11 @@ public static function getInstance( $id ) { } $className = 'Engines_' . $engineRecord->class_load; - if ( !class_exists( $className, true ) ) { - throw new Exception( "Engine Class $className not Found" ); + if ( !class_exists( $className ) ) { + $className = $engineRecord->class_load; + if( !class_exists( $className ) ){ + throw new Exception( "Engine Class $className not Found" ); + } } return new $className( $engineRecord ); @@ -48,10 +51,18 @@ public static function getInstance( $id ) { /** * @param EnginesModel_EngineStruct $engineRecord * - * @return mixed + * @return Engines_EngineInterface + * @throws Exception */ - public static function createTempInstance( EnginesModel_EngineStruct $engineRecord ) { + public static function createTempInstance( EnginesModel_EngineStruct $engineRecord ): Engines_EngineInterface { + $className = 'Engines_' . $engineRecord->class_load; + if ( !class_exists( $className ) ) { + $className = $engineRecord->class_load; + if( !class_exists( $className ) ){ + throw new Exception( "Engine Class $className not Found" ); + } + } return new $className( $engineRecord ); } diff --git a/lib/Utils/Engines/AbstractEngine.php b/lib/Utils/Engines/AbstractEngine.php index 945ac59700..35b5928f9b 100644 --- a/lib/Utils/Engines/AbstractEngine.php +++ b/lib/Utils/Engines/AbstractEngine.php @@ -26,6 +26,11 @@ abstract class Engines_AbstractEngine implements Engines_EngineInterface { protected $_isAnalysis = false; protected $_skipAnalysis = false; + /** + * @var bool True if the engine can receive contributions through a `set/update` method. + */ + protected bool $_isAdaptive = false; + /** * @var bool */ @@ -204,7 +209,6 @@ public function call( $function, array $parameters = [], $isPostRequest = false, $this->error = []; // reset last error if ( !$this->$function ) { - //Log::doJsonLog( 'Requested method ' . $function . ' not Found.' ); $this->result = [ 'error' => [ 'code' => -43, @@ -283,6 +287,10 @@ public function getEngineRow() { return clone $this->engineRecord; } + public function isAdaptive(): bool { + return $this->_isAdaptive; + } + /** * This function is PHP7 compatible * @@ -334,4 +342,9 @@ protected function GoogleTranslateFallback( $_config ) { return $gtEngine->get( $_config ); } + + public function importMemory( string $filePath, string $memoryKey, Users_UserStruct $user ){ + + } + } diff --git a/lib/Utils/Engines/DeepL.php b/lib/Utils/Engines/DeepL.php index 4713addbb9..516f38151c 100644 --- a/lib/Utils/Engines/DeepL.php +++ b/lib/Utils/Engines/DeepL.php @@ -53,6 +53,26 @@ public function get( $_config ) { $source = explode( "-", $_config[ 'source' ] ); $target = explode( "-", $_config[ 'target' ] ); + $extraParams = $this->getEngineRecord()->extra_parameters; + + if ( !isset( $extraParams[ 'DeepL-Auth-Key' ] ) ) { + throw new Exception( "DeepL API key not set" ); + } + + // glossaries (only for DeepL) + $metadataDao = new Projects_MetadataDao(); + $deepLFormality = $metadataDao->get( $_config[ 'project_id' ], 'deepl_formality', 86400 ); + $deepLIdGlossary = $metadataDao->get( $_config[ 'project_id' ], 'deepl_id_glossary', 86400 ); + + if ( $deepLFormality !== null ) { + $_config[ 'formality' ] = $deepLFormality->value; + } + + if ( $deepLIdGlossary !== null ) { + $_config[ 'idGlossary' ] = $deepLIdGlossary->value; + } + // glossaries (only for DeepL) + $parameters = [ 'text' => [ $_config['segment'], @@ -64,7 +84,7 @@ public function get( $_config ) { ]; $headers = [ - 'Authorization: DeepL-Auth-Key ' . $this->apiKey, + 'Authorization: DeepL-Auth-Key ' . $extraParams[ 'DeepL-Auth-Key' ], 'Content-Type: application/json' ]; diff --git a/lib/Utils/Engines/EngineInterface.php b/lib/Utils/Engines/EngineInterface.php index 4da2db2955..74d6fbe19c 100644 --- a/lib/Utils/Engines/EngineInterface.php +++ b/lib/Utils/Engines/EngineInterface.php @@ -52,4 +52,14 @@ public function setAnalysis(); */ public function getEngineRow(); + /** + * @return bool + */ + public function isAdaptive(): bool; + + /** + * @return mixed + */ + public function importMemory( string $filePath, string $memoryKey, Users_UserStruct $user ); + } \ No newline at end of file diff --git a/lib/Utils/Engines/Lara.php b/lib/Utils/Engines/Lara.php new file mode 100644 index 0000000000..3740592588 --- /dev/null +++ b/lib/Utils/Engines/Lara.php @@ -0,0 +1,363 @@ +engineRecord->type != "MT" ) { + throw new Exception( "Engine {$this->engineRecord->id} is not a MT engine, found {$this->engineRecord->type} -> {$this->engineRecord->class_load}" ); + } + + $this->_skipAnalysis = true; + + } + + /** + * Get MMTServiceApi client + * + * @return Translator + * @throws Exception + */ + protected function _getClient(): Translator { + + if ( !empty( $this->clientLoaded ) ) { + return $this->clientLoaded; + } + + $extraParams = $this->engineRecord->getExtraParamsAsArray(); + $credentials = new LaraCredentials( $extraParams[ 'Lara-AccessKeyId' ], $extraParams[ 'Lara-AccessKeySecret' ] ); + + $mmtStruct = EnginesModel_MMTStruct::getStruct(); + $mmtStruct->type = Constants_Engines::MT; + $mmtStruct->extra_parameters = [ + 'MMT-License' => $extraParams[ 'MMT-License' ] ?: INIT::$DEFAULT_MMT_KEY, + 'MMT-pretranslate' => true, + 'MMT-preimport' => false, + ]; + + $this->mmtUserFallback = Engine::createTempInstance( $mmtStruct ); + + $this->clientLoaded = new Translator( $credentials ); + + return $this->clientLoaded; + } + + /** + * Get the available languages in MMT + * + * @return array + * @throws LaraException + * @throws ReflectionException + * @throws Exception + */ + public function getAvailableLanguages(): array { + + $cache = ( new RedisHandler() )->getConnection(); + + $value = []; + + try { + $value = unserialize( $cache->get( "lara_languages" ) ); + } catch ( Throwable $e ) { + } + + if ( !empty( $value ) ) { + return $value; + } + + $client = $this->_getClient(); + $value = $client->getLanguages(); + $cache->setex( "lara_languages", 86400, serialize( $value ) ); + + return $value; + + } + + protected function _decode( $rawValue, array $parameters = [], $function = null ) { + // Not used since Lara works with an external client (through composer) + } + + /** + * @inheritDoc + * @throws LaraException + * @throws ReflectionException + * @throws Exception + */ + public function get( $_config ) { + + $tm_keys = TmKeyManagement_TmKeyManagement::getOwnerKeys( [ $_config[ 'all_job_tm_keys' ] ], 'r' ); + $_config[ 'keys' ] = array_map( function ( $tm_key ) { + /** + * @var $tm_key TmKeyManagement_MemoryKeyStruct + */ + return $tm_key->key; + }, $tm_keys ); + + + // init lara client and mmt fallback + $client = $this->_getClient(); + + // configuration for mmt fallback + $_config[ 'secret_key' ] = Mmt::getG2FallbackSecretKey(); + if ( $this->_isAnalysis && $this->_skipAnalysis ) { + // for MMT + $_config[ 'include_score' ] = true; + $_config[ 'priority' ] = 'background'; + + // analysis on Lara is disabled, fallback on MMT + + return $this->mmtUserFallback->get( $_config ); + } else { + $_config[ 'priority' ] = 'normal'; + } + + $_lara_keys = $this->_reMapKeyList( $_config[ 'keys' ] ); + + $languagesList = $this->getAvailableLanguages(); + + if ( in_array( $_config[ 'source' ], $languagesList ) && in_array( $_config[ 'target' ], $languagesList ) ) { + // call lara + $translateOptions = new TranslateOptions(); + $translateOptions->setAdaptTo( $_lara_keys ); + $translateOptions->setPriority( $_config[ 'priority' ] ); + $translateOptions->setMultiline( true ); + + $request_translation = []; + + foreach ( $_config[ 'context_list_before' ] as $c ) { + $request_translation[] = new TextBlock( $c, false ); + } + + $request_translation[] = new TextBlock( $_config[ 'segment' ] ); + + foreach ( $_config[ 'context_list_after' ] as $c ) { + $request_translation[] = new TextBlock( $c, false ); + } + + $time_start = microtime( true ); + $translationResponse = $client->translate( $request_translation, $_config[ 'source' ], $_config[ 'target' ], $translateOptions ); + $time_end = microtime( true ); + $time = $time_end - $time_start; + + Log::doJsonLog( [ + 'LARA REQUEST' => 'GET https://api.laratranslate.com/translate', + 'timing' => [ 'Total Time' => $time, 'Request Start Time' => $time_start, 'Request End Time' => $time_end ], + 'q' => $request_translation, + 'adapt_to' => $_lara_keys, + 'source' => $_config[ 'source' ], + 'target' => $_config[ 'target' ], + 'priority' => $_config[ 'priority' ], + 'multiline' => true, + ] ); + + $translation = ""; + $tList = $translationResponse->getTranslation(); + foreach ( $tList as $t ) { + if ( $t->isTranslatable() ) { + $translation = $t->getText(); + break; + } + } + + } else { + // mmt fallback + return $this->mmtUserFallback->get( $_config ); + } + + return ( new Engines_Results_MyMemory_Matches( + $_config[ 'segment' ], + $translation, + 100 - $this->getPenalty() . "%", + "MT-" . $this->getName(), + date( "Y-m-d" ) + ) )->getMatches( 1, [], $_config[ 'source' ], $_config[ 'target' ] ); + + } + + /** + * @inheritDoc + */ + public function set( $_config ) { + // TODO: Implement set() method. + } + + /** + * @inheritDoc + * @throws Exception + */ + public function update( $_config ) { + + $client = $this->_getClient(); + $_keys = $this->_reMapKeyList( $_config[ 'keys' ] ?? [] ); + try { + + $time_start = microtime( true ); + // call lara + $client->memories->addTranslation( + $_keys, + $_config[ 'source' ], + $_config[ 'target' ], + $_config[ 'segment' ], + $_config[ 'translation' ], + $_config[ 'tuid' ], + $_config[ 'context_before' ], + $_config[ 'context_after' ], + ); + $time_end = microtime( true ); + $time = $time_end - $time_start; + + Log::doJsonLog( [ + 'LARA REQUEST' => 'PUT https://api.laratranslate.com/memories/content', + 'timing' => [ 'Total Time' => $time, 'Request Start Time' => $time_start, 'Request End Time' => $time_end ], + 'keys' => $_keys, + 'source' => $_config[ 'source' ], + 'target' => $_config[ 'target' ], + 'sentence' => $_config[ 'segment' ], + 'translation' => $_config[ 'translation' ], + 'tuid' => $_config[ 'tuid' ], + 'sentence_before' => $_config[ 'context_before' ], + 'sentence_after' => $_config[ 'context_after' ], + ] ); + + } catch ( LaraApiException $e ) { + // Lara license expired/changed (401) or account deleted (403) + Log::doJsonLog( $e->getMessage() ); + + // DO NOT REQUEUE FOR LARA FAILURE ONLY + + } catch ( Exception $e ) { + // for any other exception (HTTP connection or timeout) requeue + return false; + } + + // let MMT to have the last word on requeue + return $this->mmtUserFallback->update( $_config ); + + } + + /** + * @throws LaraException + */ + public function importMemory( string $filePath, string $memoryKey, Users_UserStruct $user ) { + + $clientMemories = $this->_getClient()->memories; + $associatedMemories = $clientMemories->getAll(); + $memoryFound = false; + + foreach ( $associatedMemories as $memory ) { + if ( 'ext_my_' . trim( $memoryKey ) === $memory->getExternalId() ) { + $memoryFound = true; + break; + } + } + + if ( !$memoryFound ) { + return null; + } + + $fp_out = gzopen( "$filePath.gz", 'wb9' ); + + if ( !$fp_out ) { + $fp_out = null; + throw new RuntimeException( 'IOException. Unable to create temporary file.' ); + } + + $tmpFileObject = new SplFileObject( $filePath, 'r' ); + + while ( !$tmpFileObject->eof() ) { + gzwrite( $fp_out, $tmpFileObject->fgets() ); + } + + $tmpFileObject = null; + gzclose( $fp_out ); + + $clientMemories->importTmx( 'ext_my_' . $memoryKey, "$filePath.gz", true ); + + $fp_out = null; + + } + + /** + * @inheritDoc + */ + public function delete( $_config ) { + // TODO: Implement delete() method. + } + + /** + * @param array $_keys + * + * @return array + */ + protected function _reMapKeyList( array $_keys = [] ): array { + + if ( !empty( $_keys ) ) { + + if ( !is_array( $_keys ) ) { + $_keys = [ $_keys ]; + } + + $_keys = array_map( function ( $key ) { + return 'ext_my_' . $key; + }, $_keys ); + + } + + return $_keys; + + } + +} \ No newline at end of file diff --git a/lib/Utils/Engines/MMT.php b/lib/Utils/Engines/MMT.php index 6d8d54d832..7a71439683 100644 --- a/lib/Utils/Engines/MMT.php +++ b/lib/Utils/Engines/MMT.php @@ -1,6 +1,7 @@ null, 'translation' => null, @@ -45,7 +53,7 @@ public function __construct( $engineRecord ) { throw new Exception( "Engine {$this->engineRecord->id} is not a MT engine, found {$this->engineRecord->type} -> {$this->engineRecord->class_load}" ); } - if ( isset( $this->engineRecord->extra_parameters[ 'MMT-pretranslate' ] ) && $this->engineRecord->extra_parameters[ 'MMT-pretranslate' ] == true ) { + if ( isset( $this->engineRecord->extra_parameters[ 'MMT-pretranslate' ] ) && $this->engineRecord->extra_parameters[ 'MMT-pretranslate' ] ) { $this->_skipAnalysis = false; } @@ -80,7 +88,7 @@ protected function _getClient() { * Get the available languages in MMT * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function getAvailableLanguages() { $client = $this->_getClient(); @@ -92,6 +100,7 @@ public function getAvailableLanguages() { * @param $_config * * @return array|Engines_Results_AbstractResponse + * @throws ReflectionException */ public function get( $_config ) { @@ -103,6 +112,20 @@ public function get( $_config ) { $client = $this->_getClient(); $_keys = $this->_reMapKeyList( $_config[ 'keys' ] ?? [] ); + $metadata = null; + if ( !empty( $_config[ 'project_id' ] ) ) { + $metadataDao = new Projects_MetadataDao(); + $metadata = $metadataDao->setCacheTTL( 86400 )->get( $_config[ 'project_id' ], 'mmt_glossaries' ); + } + + if ( $metadata !== null ) { + $metadata = html_entity_decode( $metadata->value ); + $mmtGlossariesArray = json_decode( $metadata, true ); + + $_config[ 'glossaries' ] = implode( ",", $mmtGlossariesArray[ 'glossaries' ] ); + $_config[ 'ignore_glossary_case' ] = $mmtGlossariesArray[ 'ignore_glossary_case' ]; + } + try { $translation = $client->translate( $_config[ 'source' ], @@ -141,11 +164,11 @@ public function get( $_config ) { } /** - * @param $_keys + * @param array $_keys * * @return array */ - protected function _reMapKeyList( $_keys = [] ) { + protected function _reMapKeyList( array $_keys = [] ): array { if ( !empty( $_keys ) ) { @@ -204,7 +227,7 @@ public function set( $_config ) { public function update( $_config ) { $client = $this->_getClient(); - $_keys = $this->_reMapKeyList( @$_config[ 'keys' ] ); + $_keys = $this->_reMapKeyList( $_config[ 'keys' ] ?? [] ); try { $client->updateMemoryContent( @@ -217,7 +240,7 @@ public function update( $_config ) { $_config[ 'session' ] ); } catch ( Exception $e ) { - return false; + return false; // requeue } return true; @@ -229,55 +252,66 @@ public function delete( $_config ) { } /** - * @param $filePath - * @param $key - * @param bool $fileName * - * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @param string $filePath + * @param string $memoryKey + * @param Users_UserStruct $user * + * + * @return void + * @throws MMTServiceApiException + * @throws Exception */ - public function import( $filePath, $key, $fileName = false ) { + public function importMemory( string $filePath, string $memoryKey, Users_UserStruct $user ) { + + $client = $this->_getClient(); + + $associatedMemories = $client->getAllMemories(); + $memoryFound = false; + + foreach ( $associatedMemories as $memory ) { + if ( 'x_mm-' . trim( $memoryKey ) === $memory[ 'externalId' ] ) { + $memoryFound = true; + break; + } + } + + if ( !$memoryFound ) { + return null; + } $fp_out = gzopen( "$filePath.gz", 'wb9' ); if ( !$fp_out ) { $fp_out = null; - @unlink( $filePath ); - $filePath = null; - @unlink( "$fileName.gz" ); throw new RuntimeException( 'IOException. Unable to create temporary file.' ); } - $tmpFileObject = new \SplFileObject( $filePath, 'r' ); + $tmpFileObject = new SplFileObject( $filePath, 'r' ); while ( !$tmpFileObject->eof() ) { gzwrite( $fp_out, $tmpFileObject->fgets() ); } $tmpFileObject = null; - @unlink( $filePath ); gzclose( $fp_out ); - $client = $this->_getClient(); - $client->importIntoMemoryContent( 'x_mm-' . trim( $key ), "$filePath.gz", 'gzip' ); + $client->importIntoMemoryContent( 'x_mm-' . trim( $memoryKey ), "$filePath.gz", 'gzip' ); $fp_out = null; - @unlink( "$filePath.gz" ); - return $this->result; } /** * - * @param $file \SplFileObject + * @param $file SplFileObject * @param $source string * @param $targets string[] * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException * @internal param array $langPairs * */ - public function getContext( \SplFileObject $file, $source, $targets ) { + public function getContext( SplFileObject $file, $source, $targets ) { $fileName = $file->getRealPath(); $file->rewind(); @@ -314,7 +348,7 @@ public function getContext( \SplFileObject $file, $source, $targets ) { /** * Call to check the license key validity * @return Engines_Results_MMT_ExceptionError - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException * @throws Exception */ public function checkAccount() { @@ -334,7 +368,7 @@ public function checkAccount() { * @param $keyList TmKeyManagement_MemoryKeyStruct[] * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function connectKeys( array $keyList ) { @@ -402,7 +436,7 @@ protected function _decode( $rawValue, array $parameters = [], $function = null * @param null $externalId * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function createMemory( $name, $description = null, $externalId = null ) { $client = $this->_getClient(); @@ -417,7 +451,7 @@ public function createMemory( $name, $description = null, $externalId = null ) { * @param $id * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function deleteMemory( $id ) { $client = $this->_getClient(); @@ -430,7 +464,7 @@ public function deleteMemory( $id ) { * (id can be an external account) * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function getAllMemories() { $client = $this->_getClient(); @@ -445,7 +479,7 @@ public function getAllMemories() { * @param $id * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function getMemory( $id ) { $client = $this->_getClient(); @@ -458,7 +492,7 @@ public function getMemory( $id ) { * @param $name * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function updateMemory( $id, $name ) { $client = $this->_getClient(); @@ -471,7 +505,7 @@ public function updateMemory( $id, $name ) { * @param $data * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function importGlossary( $id, $data ) { $client = $this->_getClient(); @@ -484,7 +518,7 @@ public function importGlossary( $id, $data ) { * @param $data * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function updateGlossary( $id, $data ) { $client = $this->_getClient(); @@ -496,7 +530,7 @@ public function updateGlossary( $id, $data ) { * @param $uuid * * @return mixed - * @throws \Engines\MMT\MMTServiceApiException + * @throws MMTServiceApiException */ public function importJobStatus( $uuid ) { $client = $this->_getClient(); diff --git a/lib/Utils/Engines/MyMemory.php b/lib/Utils/Engines/MyMemory.php index 1d27392157..93be3f1ecb 100644 --- a/lib/Utils/Engines/MyMemory.php +++ b/lib/Utils/Engines/MyMemory.php @@ -15,6 +15,13 @@ */ class Engines_MyMemory extends Engines_AbstractEngine { + /** + * @inheritdoc + * @see Engines_AbstractEngine::$_isAdaptive + * @var bool + */ + protected bool $_isAdaptive = true; + /** * @var string */ @@ -606,12 +613,19 @@ public function glossaryUpdate( $idSegment, $idJob, $password, $term ) { return $this->result; } - public function import( $file, $key, $name = false ) { + /** + * + * @param string $filePath + * @param string $memoryKey + * @param Users_UserStruct $user * Not used + * +* @return array|mixed + */ + public function importMemory( string $filePath, string $memoryKey, Users_UserStruct $user) { $postFields = [ - 'tmx' => $this->getCurlFile( $file ), - 'name' => $name, - 'key' => trim( $key ) + 'tmx' => $this->getCurlFile( $filePath ), + 'key' => trim( $memoryKey ) ]; $this->call( "tmx_import_relative_url", $postFields, true ); diff --git a/lib/Utils/ProjectManager.php b/lib/Utils/ProjectManager.php index 26bc1c57d4..0b2503c56d 100644 --- a/lib/Utils/ProjectManager.php +++ b/lib/Utils/ProjectManager.php @@ -1195,8 +1195,8 @@ protected function _pushTMXToMyMemory() { $this->getSingleS3QueueFile( $fileName ); } - $this->tmxServiceWrapper->addTmxInMyMemory( $file ); - $this->features->run( 'postPushTMX', $file, $this->projectStructure[ 'id_customer' ] ); + $userStruct = ( new Users_UserDao() )->setCacheTTL( 60 * 60 )->getByUid( $this->projectStructure[ 'uid' ] ); + $this->tmxServiceWrapper->addTmxInMyMemory( $file, $userStruct ); } else { //don't call the postPushTMX for normal files diff --git a/lib/Utils/TMS/TMSService.php b/lib/Utils/TMS/TMSService.php index 1ca96283c8..1db33a930e 100644 --- a/lib/Utils/TMS/TMSService.php +++ b/lib/Utils/TMS/TMSService.php @@ -4,6 +4,7 @@ use API\Commons\Exceptions\UnprocessableException; use Chunks_ChunkDao; +use Constants_Engines; use Constants_TranslationStatus; use DateTime; use DateTimeZone; @@ -11,6 +12,7 @@ use Engines_MyMemory; use Engines_Results_MyMemory_ExportResponse; use Engines_Results_MyMemory_TmxResponse; +use EnginesModel_EngineStruct; use Exception; use FeatureSet; use INIT; @@ -22,6 +24,8 @@ use stdClass; use TMSService\TMSServiceDao; use Upload; +use Users\MetadataDao; +use Users_UserStruct; use Utils; class TMSService { @@ -143,29 +147,68 @@ public function uploadFile() { * Import TMX file in MyMemory * @throws Exception */ - public function addTmxInMyMemory( TMSFile $file ) { + public function addTmxInMyMemory( TMSFile $file, Users_UserStruct $user ) { - $this->checkCorrectKey( $file->getTmKey() ); + try { - Log::doJsonLog( $this->file ); + $this->checkCorrectKey( $file->getTmKey() ); - $importStatus = $this->mymemory_engine->import( - $file->getFilePath(), - $file->getTmKey(), - $file->getName() - ); + Log::doJsonLog( $this->file ); - //check for errors during the import - switch ( $importStatus->responseStatus ) { - case "503" : - case "400" : - throw new Exception( "Error uploading TMX file. Please, try again in 5 minutes.", -15 ); - case "403" : - throw new Exception( "Error: " . $this->formatErrorMessage( $importStatus->responseDetails ), -15 ); - default: - } + $importStatus = $this->mymemory_engine->importMemory( + $file->getFilePath(), + $file->getTmKey(), + $user + ); - $file->setUuid( $importStatus->id ); + //check for errors during the import + switch ( $importStatus->responseStatus ) { + case "503" : + case "400" : + throw new Exception( "Error uploading TMX file. Please, try again in 5 minutes.", -15 ); + case "403" : + throw new Exception( "Error: " . $this->formatErrorMessage( $importStatus->responseDetails ), -15 ); + default: + } + + $file->setUuid( $importStatus->id ); + + // load tmx in engines with adaptivity + $engineList = Constants_Engines::getAvailableEnginesList(); + + foreach ( $engineList as $engineName ) { + + try { + + $struct = EnginesModel_EngineStruct::getStruct(); + $struct->class_load = $engineName; + $struct->type = Constants_Engines::MT; + $engine = Engine::createTempInstance( $struct ); + + if ( $engine->isAdaptive() ) { + //retrieve OWNER Engine License + $ownerMmtEngineMetaData = ( new MetadataDao() )->setCacheTTL( 60 * 60 * 24 * 30 )->get( $user->uid, $engine->getEngineRow()->class_load ); // engine_id + if ( !empty( $ownerMmtEngineMetaData ) ) { + $engine = Engine::getInstance( $ownerMmtEngineMetaData->value ); + + Log::doJsonLog( "User [$user->uid, '$user->email'] start importing memory: {$engine->getEngineRow()->class_load} -> " . $file->getFilePath() . " -> " . $file->getTmKey() ); + $engine->importMemory( $file->getFilePath(), $file->getTmKey(), $user ); + + } + } + + } catch ( Exception $e ) { + if ( $engineName != Constants_Engines::MY_MEMORY ) { + Log::doJsonLog( $e->getMessage() ); + } + } + + } + + } finally { + @unlink( $file->getFilePath() ); + @unlink( $file->getFilePath() . ".gz" ); + } } diff --git a/lib/Utils/TmKeyManagement/TmKeyManagement.php b/lib/Utils/TmKeyManagement/TmKeyManagement.php index 9c1a94dff8..63662f62ac 100644 --- a/lib/Utils/TmKeyManagement/TmKeyManagement.php +++ b/lib/Utils/TmKeyManagement/TmKeyManagement.php @@ -159,7 +159,7 @@ public static function getOwnerKeys( Array $jsonTmKeys_array, $grant_level = 'rw //convert tm keys into TmKeyManagement_TmKeyStruct objects $result_arr = array_map( array( 'self', 'getTmKeyStructure' ), $result_arr ); - return $result_arr; + return array_values( $result_arr ); } /** diff --git a/lib/Utils/Utils.php b/lib/Utils/Utils.php index 1e8b7e8ef0..0bce8d24d4 100644 --- a/lib/Utils/Utils.php +++ b/lib/Utils/Utils.php @@ -814,18 +814,6 @@ public static function truncatePhrase( string $phrase, int $max_words ): string return $phrase; } - /** - * This escape is need by - * javascript JSON.parse() function - * - * @param array $data - * - * @return string - */ - public static function escapeJsonEncode( array $data ): string { - return str_replace( "\\\"", "\\\\\\\"", json_encode( $data ) ); - } - /** * This function strips html tag, but preserves hrefs. * diff --git a/lib/View/API/V2/Json/Engine.php b/lib/View/API/V2/Json/Engine.php index bfd2b95ddf..b70a337195 100644 --- a/lib/View/API/V2/Json/Engine.php +++ b/lib/View/API/V2/Json/Engine.php @@ -25,12 +25,13 @@ public function __construct( $data = null ) { * @return array */ public function renderItem( EnginesModel_EngineStruct $engine ) { + $engine_type = explode( "\\", $engine->class_load ); return [ 'id' => $engine->id, 'name' => $engine->name, 'type' => $engine->type, 'description' => $engine->description, - 'engine_type' => $engine->class_load, + 'engine_type' => array_pop( $engine_type ) ]; } diff --git a/lib/View/templates/_index.html b/lib/View/templates/_index.html index a8def86491..20cbc73f55 100755 --- a/lib/View/templates/_index.html +++ b/lib/View/templates/_index.html @@ -36,7 +36,6 @@ isOpenAiEnabled: ${isOpenAiEnabled}, maxFileSize: ${maxFileSize}, maxTMXFileSize: ${maxTMXFileSize}, - maxNumSegments: ${maxNumSegments}, id_customer: '${id_customer | string:unknown_customer}', private_customer: '${private_customer | string:0}', first_job_segment: '${first_job_segment}', @@ -49,7 +48,7 @@ analysis_enabled: '${analysis_enabled}', not_empty_default_tm_key: ${not_empty_default_tm_key|string:false}, footer_show_revise_link: ${footer_show_revise_link || string: false}, - intento_providers: JSON.parse('${translation_engines_intento_prov_json}'), + intento_providers: JSON.parse(String.raw`${php: json_encode(translation_engines_intento_providers)}`), get job_id() { console.info('job_id is deprecated, use id_job instead', getStackTrace().split("\n")[2]); return this.id_job ; @@ -85,7 +84,7 @@ mark_as_complete_button_enabled : ${mark_as_complete_button_enabled | 'false'}, status_labels : JSON.parse( '${status_labels}' ), searchable_statuses : JSON.parse( '${ php: json_encode(searchable_statuses)}'), - active_engine: JSON.parse('${active_engine}'), + active_engine: JSON.parse(String.raw`${php: json_encode(active_engine)}`), isGDriveProject : ${isGDriveProject|string:false}, remoteFilesInJob : JSON.parse( '${ php: json_encode(remoteFilesInJob)}'), translation_matches_enabled : ${translation_matches_enabled} , diff --git a/plugins/aligner b/plugins/aligner index 5c41058446..7adf6b326e 160000 --- a/plugins/aligner +++ b/plugins/aligner @@ -1 +1 @@ -Subproject commit 5c410584467816b0df746d17e5462b440ecb22f6 +Subproject commit 7adf6b326e5ea91e93b857e9a94f3661cf4a020a diff --git a/public/css/sass/components/settingsPanel/MachineTranslationTab.scss b/public/css/sass/components/settingsPanel/MachineTranslationTab.scss index 8dfefd3a5f..91ea207ee7 100644 --- a/public/css/sass/components/settingsPanel/MachineTranslationTab.scss +++ b/public/css/sass/components/settingsPanel/MachineTranslationTab.scss @@ -125,6 +125,12 @@ } } } + + .provider-field-row { + display: flex; + flex-direction: column; + gap: 4px; + } } .machine-translation-tab-table-title { display: flex; @@ -196,6 +202,21 @@ .add-mt-provider .select-with-label__wrapper { margin-right: 30px; } + + .provider-license-label-with-icon { + display: flex; + gap: 5px; + } + + .provider-data-lara { + .button { + margin-top: 62px !important; + } + + .provider-field { + gap: 15px !important; + } + } } .machine-translation-tab { .settings-panel-table-rowHeading, diff --git a/public/js/cat_source/es6/api/convertFileRequest/convertFileRequest.js b/public/js/cat_source/es6/api/convertFileRequest/convertFileRequest.js index 39409877fb..7cf99724ad 100644 --- a/public/js/cat_source/es6/api/convertFileRequest/convertFileRequest.js +++ b/public/js/cat_source/es6/api/convertFileRequest/convertFileRequest.js @@ -19,7 +19,7 @@ export const convertFileRequest = async ({ segmentation_rule, filters_extraction_parameters_template_id, signal, - restarted_conversion + restarted_conversion, }) => { const dataParams = { action, @@ -28,7 +28,7 @@ export const convertFileRequest = async ({ target_lang, segmentation_rule, filters_extraction_parameters_template_id, - restarted_conversion + restarted_conversion, } const formData = new FormData() diff --git a/public/js/cat_source/es6/api/getContributions/getContributions.js b/public/js/cat_source/es6/api/getContributions/getContributions.js index 5886f30209..152d8bc72b 100644 --- a/public/js/cat_source/es6/api/getContributions/getContributions.js +++ b/public/js/cat_source/es6/api/getContributions/getContributions.js @@ -22,19 +22,20 @@ export const getContributions = async ({ password = config.password, idClient = config.id_client, currentPassword = config.currentPassword, + contextListBefore, + contextListAfter, }) => { const contextBefore = UI.getContextBefore(idSegment) const idBefore = UI.getIdBefore(idSegment) const contextAfter = UI.getContextAfter(idSegment) const idAfter = UI.getIdAfter(idSegment) - const txt = target const obj = { action: 'getContribution', password: password, is_concordance: 0, id_segment: idSegment, - text: txt, + text: target, id_job: idJob, num_results: NUM_CONTRIBUTION_RESULTS, context_before: contextBefore ? contextBefore : '', @@ -44,6 +45,8 @@ export const getContributions = async ({ id_client: idClient, cross_language: crossLanguages, current_password: currentPassword, + context_list_before: JSON.stringify(contextListBefore), + context_list_after: JSON.stringify(contextListAfter), } const dataParams = Object.fromEntries( Object.entries(obj).filter(([_, v]) => v != null), diff --git a/public/js/cat_source/es6/components/segments/SegmentFooterTabMatches.js b/public/js/cat_source/es6/components/segments/SegmentFooterTabMatches.js index 7fd07f6e40..9346caf931 100644 --- a/public/js/cat_source/es6/components/segments/SegmentFooterTabMatches.js +++ b/public/js/cat_source/es6/components/segments/SegmentFooterTabMatches.js @@ -37,7 +37,7 @@ class SegmentFooterTabMatches extends React.Component { var item = {} item.id = this.id item.disabled = this.id == '0' ? true : false - item.cb = this.created_by + item.cb = this.created_by === 'MT-Lara' ? 'Lara' : this.created_by item.segment = this.segment item.translation = this.translation item.target = this.target diff --git a/public/js/cat_source/es6/components/segments/utils/translationMatches.js b/public/js/cat_source/es6/components/segments/utils/translationMatches.js index 30d00bf843..567cb135fb 100644 --- a/public/js/cat_source/es6/components/segments/utils/translationMatches.js +++ b/public/js/cat_source/es6/components/segments/utils/translationMatches.js @@ -189,13 +189,16 @@ let TranslationMatches = { console.log('SSE: ID_CLIENT not found') return Promise.resolve() } - + const {contextListBefore, contextListAfter} = + SegmentUtils.getSegmentContext(id_segment_original) return getContributions({ idSegment: id_segment_original, target: currentSegment.segment, crossLanguages: crossLanguageSettings ? [crossLanguageSettings.primary, crossLanguageSettings.secondary] : [], + contextListBefore, + contextListAfter, }).catch((errors) => { UI.processErrors(errors, 'getContribution') TranslationMatches.renderContributionErrors(errors, id_segment_original) diff --git a/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MTRow.js b/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MTRow.js index 8932be6fc8..205a0f9327 100644 --- a/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MTRow.js +++ b/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MTRow.js @@ -36,6 +36,11 @@ export const MTRow = ({row, deleteMT, onCheckboxClick}) => { )} + {row.engine_type === 'Lara' && ( + + + + )}
{row.description}
{!config.is_cattool && ( diff --git a/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MachineTranslationTab.js b/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MachineTranslationTab.js index 2701668d76..09f54f68bd 100644 --- a/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MachineTranslationTab.js +++ b/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MachineTranslationTab.js @@ -28,12 +28,12 @@ import {ConfirmDeleteResourceProjectTemplates} from '../../../modals/ConfirmDele import {SCHEMA_KEYS} from '../../../../hooks/useProjectTemplates' import IconClose from '../../../icons/IconClose' import {BUTTON_TYPE, Button} from '../../../common/Button/Button' +import {Lara} from './MtEngines/Lara' export const MachineTranslationTab = () => { const { mtEngines, setMtEngines, - openLoginModal, modifyingCurrentTemplate, currentProjectTemplate, projectTemplates, @@ -65,6 +65,11 @@ export const MachineTranslationTab = () => { id: 'mmt', component: ModernMt, }, + { + name: 'Lara', + id: 'Lara', + component: Lara, + }, {name: 'AltLang', id: 'altlang', component: AltLang}, {name: 'Apertium', id: 'apertium', component: Apertium}, {name: 'DeepL', id: 'deepl', component: DeepL}, @@ -364,10 +369,12 @@ export const MachineTranslationTab = () => { } /> -
-

Inactive MT

- -
+ {!config.is_cattool && ( +
+

Inactive MT

+ +
+ )} ) } diff --git a/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MtEngines/Lara.js b/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MtEngines/Lara.js new file mode 100644 index 0000000000..508e478d1f --- /dev/null +++ b/public/js/cat_source/es6/components/settingsPanel/Contents/MachineTranslationTab/MtEngines/Lara.js @@ -0,0 +1,116 @@ +import React, {useRef} from 'react' +import {useForm} from 'react-hook-form' +import Tooltip from '../../../../common/Tooltip' +import InfoIcon from '../../../../../../../../img/icons/InfoIcon' + +export const Lara = ({addMTEngine, error, isRequestInProgress}) => { + const infoIcon1 = useRef() + + const { + register, + handleSubmit, + watch, + formState: {errors}, + } = useForm() + + const laraAccessKeyID = watch('lara-access-key-id') + const laraLicense = watch('secret') + + const onSubmit = (data) => { + addMTEngine(data) + } + return ( +
+
+
+
+
+ + +
+
+ + +
+
+
+ + + (Optional) Enter your ModernMT license to use your + personal engine for language combinations not supported by + Lara +
+ } + > +
+ +
+ +
+ +
+ {errors.secret && ( + Required field + )} +
+
+ {error && {error.message}} + +
+
+
+
+

+ Lara is a groundbreaking machine translation engine + powered by Large Language Models. It surpasses traditional machine + translation by understanding context and learning from previously + translated content, delivering high-quality, nuanced translations. +
+ Lara currently supports all combinations of ten languages, with more + on the way soon. For languages not yet supported, translation is + provided through ModernMT Lite, or your personal ModernMT Full if a + valid license is provided. +
+ Lara is only available for machine translation post-editing, + pre-translation is always performed with ModernMT. +

+ + + Learn More + +
+ + ) +} diff --git a/public/js/cat_source/es6/stores/SegmentStore.js b/public/js/cat_source/es6/stores/SegmentStore.js index 90dc09bd52..e826eba893 100644 --- a/public/js/cat_source/es6/stores/SegmentStore.js +++ b/public/js/cat_source/es6/stores/SegmentStore.js @@ -44,6 +44,7 @@ import { REVISE_STEP_NUMBER, SEGMENTS_STATUS, } from '../constants/Constants' +import segment from '../components/segments/Segment' EventEmitter.prototype.setMaxListeners(0) @@ -1135,11 +1136,7 @@ const SegmentStore = assign({}, EventEmitter.prototype, { }, getAllSegments: function () { - var result = [] - $.each(this._segments, function (key, value) { - result = result.concat(value.toJS()) - }) - return result + return this._segments.toJS() }, getSegmentById(sid) { return this._segments.find(function (seg) { diff --git a/public/js/cat_source/es6/utils/lxq.main.js b/public/js/cat_source/es6/utils/lxq.main.js index 2a2b6e7200..27c80478d3 100644 --- a/public/js/cat_source/es6/utils/lxq.main.js +++ b/public/js/cat_source/es6/utils/lxq.main.js @@ -733,9 +733,9 @@ LXQ.init = function () { } var notCheckedSegments //store the unchecked segments at startup var doQAallSegments = function () { - var segments = SegmentStore.getAllSegments + var segments = SegmentStore.getAllSegments() var notChecked = [] - $.each(segments, function (keys, segment) { + segments.forEach((segment) => { var segId = segment.sid if (LXQ.lexiqaData.segments.indexOf(segId) < 0) { notChecked.push(segment) diff --git a/public/js/cat_source/es6/utils/segmentUtils.js b/public/js/cat_source/es6/utils/segmentUtils.js index defa2f60e8..a23ab44243 100644 --- a/public/js/cat_source/es6/utils/segmentUtils.js +++ b/public/js/cat_source/es6/utils/segmentUtils.js @@ -167,6 +167,24 @@ const SegmentUtils = { }) return statuses }, + getSegmentContext: (sid) => { + const segments = SegmentStore.getAllSegments() + const segmentIndex = SegmentStore.getSegmentIndex(sid) + if (segmentIndex === -1) { + throw new Error('Segment not found.') + } + + const beforeStartIndex = Math.max(0, segmentIndex - 5) + const beforeElements = segments.slice(beforeStartIndex, segmentIndex) + + const afterEndIndex = Math.min(segments.length, segmentIndex + 3) + const afterElements = segments.slice(segmentIndex + 1, afterEndIndex) + + return { + contextListBefore: beforeElements.map((segment) => segment.segment), + contextListAfter: afterElements.map((segment) => segment.segment), + } + }, createSetTranslationRequest: (segment, status, propagate = false) => { let {translation, segment: segmentSource, original_sid: sid} = segment const contextBefore = UI.getContextBefore(sid) diff --git a/public/js/upload_main.js b/public/js/upload_main.js index d61c17a235..a7f9ee1cf3 100644 --- a/public/js/upload_main.js +++ b/public/js/upload_main.js @@ -396,7 +396,7 @@ window.UI = { fileSpecs.filerow, fileSpecs.filesize, fileSpecs.enforceConversion, - false + false, ) } } else { @@ -495,7 +495,13 @@ var progressBar = function (filerow, start, filesize) { } } -var convertFile = function (fname, filerow, filesize, enforceConversion, restartedConversion) { +var convertFile = function ( + fname, + filerow, + filesize, + enforceConversion, + restartedConversion, +) { console.log('Restarted conversion: ' + restartedConversion) console.log('Enforce conversion: ' + enforceConversion) enforceConversion = @@ -532,7 +538,7 @@ var convertFile = function (fname, filerow, filesize, enforceConversion, restart segmentation_rule: UI.segmentationRule, filters_extraction_parameters_template_id: filtersTemplate?.id, signal, - restarted_conversion: restartedConversion + restarted_conversion: restartedConversion, }) .then(function ({data, errors}) { filerow.removeClass('converting') diff --git a/public/mocks/segmentsMock.js b/public/mocks/segmentsMock.js new file mode 100644 index 0000000000..fc6ce7d05a --- /dev/null +++ b/public/mocks/segmentsMock.js @@ -0,0 +1,2335 @@ +export const segmentsMock = [ + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83040', + segment: 'Refer to your statement for accurate billing information.', + segment_hash: '10c2ed89538d4ee7fcd6f9d6f5a16335', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83041', + segment: 'Unable to load data usage', + segment_hash: '8278f3788fd5f52101426b460a13d3d2', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83042', + segment: 'Change service plan', + segment_hash: '1bf4050283e2d22edbcbc6c30708280b', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83043', + segment: 'No alternative plans found for this Starlink', + segment_hash: 'b85ee80eb9465472cb4baae242a4b5ae', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83044', + segment: 'Unable to load service plans', + segment_hash: '3b17dd987441e848a57079596f1ed8a8', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83045', + segment: 'Due today', + segment_hash: '9209f1b49d0c1f209ed6d6b677f06817', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83046', + segment: + 'Difference in the price of new plan and current plan, prorated until the remainder of the billing period, due today.', + segment_hash: '3a98a7f34426f85b245051e5f18582d7', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83047', + segment: 'New plan will take effect within two hours.', + segment_hash: '0c843b463474be19449a19edbf56ebb4', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83048', + segment: 'New plan will take effect with the next billing period.', + segment_hash: '50306d8fd1f09d367bfd86e21242e030', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83049', + segment: 'New plan will take effect {planStartDate}', + segment_hash: '90c855e931c0b48e183b6c5195466a09', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83050', + segment: 'Changing plan', + segment_hash: '61cf1e927b0c9fd18f96da1eeef3223a', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83051', + segment: 'Switch plan', + segment_hash: '71ade35958f0518654eb22483664fab4', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83052', + segment: 'Unable to load service plan', + segment_hash: '5d57cf08bb67356af9cdbf9812376807', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83053', + segment: 'Cancel upcoming plan change', + segment_hash: '237866dc75a08d59f291c3f07067b036', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83054', + segment: 'Manage your subscription at', + segment_hash: '4f9d379d380c1ea531b8ef491b6e0f0c', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83055', + segment: 'Service address', + segment_hash: '42fd4991088abcac031680aecdbcfd5c', + translation: 'Indirizzo assistenza', + ice_locked: '0', + status: 'DRAFT', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83056', + segment: 'Today', + segment_hash: '1dd1c5fb7f25cd41b291d43a89e3aefd', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83057', + segment: 'Unlimited', + segment_hash: '545f6c2f382c04810103b3e5e6f7d841', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83058', + segment: 'Opt-in', + segment_hash: '1d1453cfa861e686dd9fb036740a54af', + translation: 'Accetta', + ice_locked: '0', + status: 'DRAFT', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83059', + segment: 'Opt-in ({overagePricePerGB})', + segment_hash: '304f3323ea0d1dad81fbbad66612caa4', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83060', + segment: '{usageLimit} included', + segment_hash: 'c99d98105663fabff6fd4324bdd5799f', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83061', + segment: 'Total data usage', + segment_hash: 'a06a5453cb920f6eb3e19b1784e39438', + translation: 'Utilizzo dei dati', + ice_locked: '0', + status: 'DRAFT', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83062', + segment: + 'Your Starlink was reporting additional troubleshooting data on some days during this billing period. ', + segment_hash: '3347e72ef37dcb2109d4206a4777632a', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83063', + segment: + "Data on these days will not be counted towards your totals and won't be billed.", + segment_hash: '7d33fc584d5f1ed8272fe92b7c5272a3', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83064', + segment: 'Unable to save', + segment_hash: '285dea00d372b99997e54a0157b71012', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83065', + segment: 'Nickname', + segment_hash: '1771b8284280660537e3e533f88af3b2', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83066', + segment: 'Priority Data', + segment_hash: '221bc184221d4fb557ccc8932ebfa095', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83067', + segment: + 'Opt in to {overageName} to receive additional {unrestrictedDataName} after exceeding {usageLimit} of included {unrestrictedDataName}.', + segment_hash: '99cd72bc243be9ba46277113f319040c', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83068', + segment: 'Opt in to receive {unrestrictedDataName}.', + segment_hash: '32873fe8619574460c18d3795582aa25', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83069', + segment: + "You'll continue to receive {unrestrictedDataName} after exceeding {usageLimit} of included {unrestrictedDataName}.", + segment_hash: 'dad60bd45747e17b8037ed88e6aabf1c', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83070', + segment: "You're receiving {unrestrictedDataName}.", + segment_hash: '3b0133ed5990ef68f6562c386e4e83ee', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83071', + segment: 'Additional data is billed per GB.', + segment_hash: '08dbf5a576ddb831f86d866835eefbbb', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83072', + segment: 'Included', + segment_hash: '984ceac16fab685fc50b8d8a5eabf7de', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83073', + segment: 'Opt-in##$_SPLIT$## data', + segment_hash: '4e372e58f50f766cef7b6dca83d26618', + translation: 'Dati opt-in dfdf', + ice_locked: '0', + status: 'DRAFT', + time_to_edit: '41', + parsed_time_to_edit: ['00', '00', '00', 41], + warning: '0', + source_chunk_lengths: [0, 6, 5], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT', 'DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '1', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83074', + segment: 'Opt-out confirmed. ', + segment_hash: 'fab987ef34d6e3dc275bbfc12b07cee2', + translation: 'Opt-out confermato. ciao ', + ice_locked: '0', + status: 'TRANSLATED', + time_to_edit: '10605', + parsed_time_to_edit: ['00', '00', '10', 605], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '1', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83075', + segment: 'Update in progress', + segment_hash: 'a16423b16062fefa352e2b8828d941a7', + translation: 'Aggiornamento in corso ciao', + ice_locked: '0', + status: 'TRANSLATED', + time_to_edit: '11481', + parsed_time_to_edit: ['00', '00', '11', 481], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '2', + revision_number: null, + notes: null, + version_number: '1', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83076', + segment: 'Learn more', + segment_hash: 'd59048f21fd887ad520398ce677be586', + translation: 'Scopri di più dsfsd', + ice_locked: '0', + status: 'TRANSLATED', + time_to_edit: '30717', + parsed_time_to_edit: ['00', '00', '30', 717], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '5', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83077', + segment: + "All data after {usageLimit} of your plan's {overageName} allowance will be automatically charged on your bill until {overageName} is turned off.", + segment_hash: '2ccb7c8285ed96ee3dd39ca5730a01fc', + translation: + "Tutti i dati dopo {usageLimit} dell'indennità {overageName} del tuo piano verranno addebitati automaticamente sulla tua fattura fino a quando {overageName} non verrà disattivato.", + ice_locked: '0', + status: 'TRANSLATED', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83078', + segment: + 'All data will be automatically charged on your bill until {overageName} is turned off.', + segment_hash: '29662278d1354a078293623ac094b9e3', + translation: + 'Tutti i dati verranno addebitati automaticamente sulla tua fattura fino a quando {overageName} non verrà disattivato.', + ice_locked: '0', + status: 'TRANSLATED', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83079', + segment: 'Enable {overageName}?', + segment_hash: '23dc55ed705be05b2f29c37c0c003fba', + translation: 'Abilitare {overageName}?', + ice_locked: '0', + status: 'TRANSLATED', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83080', + segment: 'Enable', + segment_hash: '2faec1f9f8cc7f8f40d521c4dd574f49', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83081', + segment: '{overageName} will begin within 15 minutes.', + segment_hash: '911d641edbe805ee723f360a15ba4703', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83082', + segment: 'Opt-in confirmed. ', + segment_hash: 'a290c970010cd50fdf341c0f9d1a8c9a', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83083', + segment: 'Update in progress', + segment_hash: 'a16423b16062fefa352e2b8828d941a7', + translation: 'Aggiornamento in corso ciao', + ice_locked: '0', + status: 'TRANSLATED', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '83075', + repetitions_in_chunk: '2', + revision_number: null, + notes: null, + version_number: '1', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83084', + segment: 'Opt out of {overageName}?', + segment_hash: 'ffd36b1884c6cd3a59342826a7997e03', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83085', + segment: '{overageName} will continue for up to 15 minutes.', + segment_hash: '6247c6b9733ec6aebc29cf469d904bda', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83086', + segment: 'Turn off', + segment_hash: '8e61d99ca246955cc31b67a365b2a216', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83087', + segment: 'Keep on', + segment_hash: '40db59a0e9844707ddea2ed69584575f', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83088', + segment: '{price} / mo', + segment_hash: '1b8d11eccc294881cea872110b6f9c00', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83089', + segment: 'Current plan ending this billing cycle', + segment_hash: '2fb9d73ef100a81120f129b5f4001a8f', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83090', + segment: 'Current plan ending {planEnds}', + segment_hash: '8fc63dafe395d4901f282f59ce3a770b', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83091', + segment: 'Upcoming plan starting next billing cycle', + segment_hash: '338cb42f32a3b9b9ce7c3a17c3636bcd', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83092', + segment: 'Upcoming plan starting {planStarts}', + segment_hash: '4a425f12bf97c9310cfad0428b3d5731', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83093', + segment: 'Current plan', + segment_hash: '441f54c7f0ecef11a8a652c6ed75c52e', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83094', + segment: 'Manage subscription', + segment_hash: '97788cfaf9dabfbe68578f0e5a637b5e', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83095', + segment: 'Mobile Data', + segment_hash: '693bacddf5f0d317289d7153fb40ac1e', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83096', + segment: 'Mobile Priority Data', + segment_hash: '05377be57758ad9833dace2e42c1d663', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83097', + segment: 'Standard Data', + segment_hash: '45f1e0cecdd9a933a2852a714f508cfc', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83098', + segment: 'Non-billable Data', + segment_hash: '72be6d556917302733ca0c98ef74bd81', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83099', + segment: 'Wrong', + segment_hash: 'b35e5a1e003084f6f4268ed1c8abceb5', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83100', + segment: 'Unhelpful', + segment_hash: '901b4bf4d33435279029f01d09cba36e', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83101', + segment: 'Unsafe', + segment_hash: 'd3d57868b6ff9839eff631d2cc8acbce', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83102', + segment: 'Report received', + segment_hash: '286cbdee035c44e53736dc745b85b296', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83103', + segment: 'Thank you for reporting an issue!', + segment_hash: 'c500021ad1a1206bece6a0cd8147c4f6', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83104', + segment: 'Send report', + segment_hash: '98892a7db1a3904050e1fb7fd1e5e008', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83105', + segment: 'The entire conversation will be sent with your report.', + segment_hash: 'ac7e8734b5ed78062eff0c9c981384f2', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83106', + segment: 'Report an issue', + segment_hash: '1ed0e75eff9abebaaab606a416c58707', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83107', + segment: 'Select a report reason', + segment_hash: 'eba44fbc8a67ef3f11bc111954f678ea', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83108', + segment: + 'By sending a message containing personal information, you consent to Starlink collecting and sharing it as described in our [Privacy Policy]({privacyPolicyUrl}).', + segment_hash: 'df2a39fadf4863fc7b3b6f450ba37908', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83109', + segment: 'About the chatbot', + segment_hash: '29d543f456c4a44b65035071d7dfb85c', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83110', + segment: 'Our support chatbot can quickly answer your Starlink questions.', + segment_hash: '866cf792e5305d6df5a72241b2150bfe', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83111', + segment: + "This chatbot is experimental and we're constantly working to iron out the kinks, so use the flag button to provide feedback!", + segment_hash: '0f0626690e101046de4bb8bcbceed7c5', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83112', + segment: + 'If you need to provide any personal or sensitive information to answer your question, we recommend opening a support ticket instead.', + segment_hash: '5c21d04d196b9f030c1e0fbcc3dd7a65', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83113', + segment: 'Do you need help from a human?', + segment_hash: '535a998a620f7187f4fbd30906e9d844', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83114', + segment: 'Starlink Support', + segment_hash: '65b8a5898a039de1f7047b85e665d349', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83115', + segment: 'Experimental customer support chatbot', + segment_hash: 'a0cb4c5f1f5164c9d576be9bf86ca4cc', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83116', + segment: 'Starlink Support Chatbot', + segment_hash: '7796bb8fe1098d914c5929981adc46c0', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83117', + segment: 'Flag this response', + segment_hash: 'e3e40f062052d91bd8cc2a63126efce5', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83118', + segment: + "Thank you for letting us know this article didn't solve your issue. ", + segment_hash: 'f63ce617954191a08fa16a296a1a6ddb', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, + { + jid: '142', + id_file: '142', + id_file_part: '6683', + filename: 'App file - retranslation.json', + sid: '83119', + segment: "We'll use this feedback to improve our articles.", + segment_hash: 'd5172deee4acb28ccf6e75d5781b83a7', + translation: '', + ice_locked: '0', + status: 'NEW', + time_to_edit: '0', + parsed_time_to_edit: ['00', '00', '00', '00'], + warning: '0', + source_chunk_lengths: [], + target_chunk_lengths: { + len: [0], + statuses: ['DRAFT'], + }, + readonly: 'false', + autopropagated_from: '0', + repetitions_in_chunk: '1', + revision_number: null, + notes: null, + version_number: '0', + data_ref_map: null, + context_groups: null, + metadata: [], + }, +] diff --git a/tests/unit/TestContribution/SetContributionMTWorkerTest.php b/tests/unit/TestContribution/SetContributionMTWorkerTest.php index 4962df01de..68bcea327e 100644 --- a/tests/unit/TestContribution/SetContributionMTWorkerTest.php +++ b/tests/unit/TestContribution/SetContributionMTWorkerTest.php @@ -121,6 +121,7 @@ public function test_ExecContribution_WillCall_MMT_With_single_tm_key() { //create a stub Engine MMT $stubEngine = @$this->getMockBuilder( '\Engines_MMT' ) ->disableOriginalConstructor() + ->onlyMethods(['update', 'getEngineRow']) ->getMock(); $engineStruct = new EnginesModel_EngineStruct(); @@ -182,11 +183,13 @@ public function test_ExecContribution_WillCall_MMT_With_single_tm_key() { $inspector = new InvocationInspector( $stubEngineParameterSpy ); $invocations = $inspector->getInvocations(); - $this->assertEquals( $this->contributionStruct->segment, $invocations[ 0 ]->getParameters()[ 0 ][ 'segment' ] ); - $this->assertEquals( [ 'XXXXXXXXXXXXXXXX' ], $invocations[ 0 ]->getParameters()[ 0 ][ 'keys' ] ); - $this->assertEquals( '1999999:9876', $invocations[ 0 ]->getParameters()[ 0 ][ 'tuid' ] ); - $this->assertEquals( 'ed1814ac9699c651fdfca4912b1b6729', $invocations[ 0 ]->getParameters()[ 0 ][ 'session' ] ); + if(!empty($invocations)){ + $this->assertEquals( $this->contributionStruct->segment, $invocations[ 0 ]->getParameters()[ 0 ][ 'segment' ] ); + $this->assertEquals( [ 'XXXXXXXXXXXXXXXX' ], $invocations[ 0 ]->getParameters()[ 0 ][ 'keys' ] ); + $this->assertEquals( '1999999:9876', $invocations[ 0 ]->getParameters()[ 0 ][ 'tuid' ] ); + $this->assertEquals( 'ed1814ac9699c651fdfca4912b1b6729', $invocations[ 0 ]->getParameters()[ 0 ][ 'session' ] ); + } } /** @@ -202,7 +205,10 @@ public function test_ExecContribution_WillCall_MMT_With_multiple_tm_keys() { $_worker->attach( $this ); //create a stub Engine MyMemory - $stubEngine = @$this->getMockBuilder( '\Engines_MMT' )->disableOriginalConstructor()->getMock(); + $stubEngine = @$this->getMockBuilder( '\Engines_MMT' ) + ->onlyMethods(['update', 'getEngineRow']) + ->disableOriginalConstructor() + ->getMock(); $engineStruct = new EnginesModel_EngineStruct(); $engineStruct->id = 1111; @@ -262,11 +268,12 @@ public function test_ExecContribution_WillCall_MMT_With_multiple_tm_keys() { $inspector = new InvocationInspector( $stubEngineParameterSpy ); $invocations = $inspector->getInvocations(); - $this->assertEquals( $this->contributionStruct->segment, $invocations[ 0 ]->getParameters()[ 0 ][ 'segment' ] ); - $this->assertEquals( [ 'XXXXXXXXXXXXXXXXXXX', 'YYYYYYYYYYYYYYYYYYYY' ], $invocations[ 0 ]->getParameters()[ 0 ][ 'keys' ] ); - $this->assertEquals( '1999999:9876', $invocations[ 0 ]->getParameters()[ 0 ][ 'tuid' ] ); - $this->assertEquals( 'ed1814ac9699c651fdfca4912b1b6729', $invocations[ 0 ]->getParameters()[ 0 ][ 'session' ] ); - + if(!empty($invocations)){ + $this->assertEquals( $this->contributionStruct->segment, $invocations[ 0 ]->getParameters()[ 0 ][ 'segment' ] ); + $this->assertEquals( [ 'XXXXXXXXXXXXXXXXXXX', 'YYYYYYYYYYYYYYYYYYYY' ], $invocations[ 0 ]->getParameters()[ 0 ][ 'keys' ] ); + $this->assertEquals( '1999999:9876', $invocations[ 0 ]->getParameters()[ 0 ][ 'tuid' ] ); + $this->assertEquals( 'ed1814ac9699c651fdfca4912b1b6729', $invocations[ 0 ]->getParameters()[ 0 ][ 'session' ] ); + } } /** @@ -284,7 +291,10 @@ public function testWorker_WillCall_Engine_NONE_With_No_TM_Engine_Configured() { /** * @var $queueElement Contribution\ContributionSetStruct */ - $contributionMockQueueObject = @$this->getMockBuilder( '\Contribution\ContributionSetStruct' )->getMock(); + $contributionMockQueueObject = @$this + ->getMockBuilder( '\Contribution\ContributionSetStruct' ) + ->onlyMethods(['getJobStruct']) + ->getMock(); $contributionMockQueueObject->expects( $this->once() ) ->method( 'getJobStruct' ) @@ -327,7 +337,11 @@ public function testExceptionForEngineSetFailure() { $_worker->attach( $this ); //create a stub Engine MyMemory - $stubEngine = @$this->getMockBuilder( '\Engines_MMT' )->disableOriginalConstructor()->getMock(); + $stubEngine = @$this + ->getMockBuilder( '\Engines_MMT' ) + ->onlyMethods(['update', 'getEngineRow']) + ->disableOriginalConstructor() + ->getMock(); $engineStruct = new EnginesModel_EngineStruct(); $engineStruct->id = 0; diff --git a/tests/unit/TestContribution/SetContributionWorkerTest.php b/tests/unit/TestContribution/SetContributionWorkerTest.php index 2296837f5d..0ae19b5a83 100644 --- a/tests/unit/TestContribution/SetContributionWorkerTest.php +++ b/tests/unit/TestContribution/SetContributionWorkerTest.php @@ -113,7 +113,11 @@ public function test_ExecContribution_WillCall_MemoryEngine_With_single_tm_key() $_worker->attach( $this ); //create a stub Engine MyMemory - $stubEngine = @$this->getMockBuilder( '\Engines_MyMemory' )->disableOriginalConstructor()->getMock(); + $stubEngine = @$this + ->getMockBuilder( '\Engines_MyMemory' ) + ->onlyMethods(['update', 'getEngineRow']) + ->disableOriginalConstructor() + ->getMock(); $engineStruct = new EnginesModel_EngineStruct(); $engineStruct->id = 1; @@ -136,7 +140,11 @@ public function test_ExecContribution_WillCall_MemoryEngine_With_single_tm_key() /** * @var $queueElement Contribution\ContributionSetStruct */ - $contributionMockQueueObject = @$this->getMockBuilder( '\Contribution\ContributionSetStruct' )->getMock(); + $contributionMockQueueObject = @$this + ->getMockBuilder( '\Contribution\ContributionSetStruct' ) + ->disableOriginalConstructor() + ->onlyMethods(['getProp', 'getJobStruct']) + ->getMock(); $contributionMockQueueObject->expects( $this->once() )->method( 'getProp' ); $contributionMockQueueObject->expects( $this->once() ) ->method( 'getJobStruct' ) @@ -189,7 +197,11 @@ public function test_ExecContribution_WillCall_MemoryEngine_With_multiple_tm_key $_worker->attach( $this ); //create a stub Engine MyMemory - $stubEngine = @$this->getMockBuilder( '\Engines_MyMemory' )->disableOriginalConstructor()->getMock(); + $stubEngine = @$this + ->getMockBuilder( '\Engines_MyMemory' ) + ->onlyMethods(['update', 'getEngineRow']) + ->disableOriginalConstructor() + ->getMock(); $engineStruct = new EnginesModel_EngineStruct(); $engineStruct->id = 1; @@ -212,7 +224,12 @@ public function test_ExecContribution_WillCall_MemoryEngine_With_multiple_tm_key /** * @var $queueElement Contribution\ContributionSetStruct */ - $contributionMockQueueObject = @$this->getMockBuilder( '\Contribution\ContributionSetStruct' )->getMock(); + $contributionMockQueueObject = @$this + ->getMockBuilder( '\Contribution\ContributionSetStruct' ) + ->disableOriginalConstructor() + ->onlyMethods(['getProp', 'getJobStruct']) + ->getMock(); + $contributionMockQueueObject->expects( $this->once() )->method( 'getProp' ); $contributionMockQueueObject->expects( $this->once() ) ->method( 'getJobStruct' ) @@ -267,23 +284,26 @@ public function testWorker_WillCall_Engine_NONE_With_No_TM_Engine_Configured() { /** * @var $queueElement Contribution\ContributionSetStruct */ - $contributionMockQueueObject = @$this->getMockBuilder( '\Contribution\ContributionSetStruct' )->getMock(); - - $contributionMockQueueObject->expects( $this->once() )->method( 'getProp' ); - $contributionMockQueueObject->expects( $this->once() ) - ->method( 'getJobStruct' ) - ->willReturn( - new Jobs_JobStruct( - [ - 'id' => $this->contributionStruct->id_job, - 'password' => $this->contributionStruct->job_password, - 'source' => 'en-US', - 'target' => 'it-IT', - 'id_tms' => 0, - 'tm_keys' => '[]' - ] - ) - ); + $contributionMockQueueObject = @$this + ->getMockBuilder( '\Contribution\ContributionSetStruct' ) + ->disableOriginalConstructor() + ->onlyMethods(['getProp', 'getJobStruct']) + ->getMock(); + + $contributionMockQueueObject + ->method( 'getJobStruct' ) + ->willReturn( + new Jobs_JobStruct( + [ + 'id' => $this->contributionStruct->id_job, + 'password' => $this->contributionStruct->job_password, + 'source' => 'en-US', + 'target' => 'it-IT', + 'id_tms' => 0, + 'tm_keys' => '[]' + ] + ) + ); $reflectedMethod = new ReflectionMethod( $_worker, '_execContribution' ); $reflectedMethod->setAccessible( true ); @@ -310,7 +330,11 @@ public function testExceptionForMyMemorySetFailure() { $_worker->attach( $this ); //create a stub Engine MyMemory - $stubEngine = @$this->getMockBuilder( '\Engines_MyMemory' )->disableOriginalConstructor()->getMock(); + $stubEngine = @$this + ->getMockBuilder( '\Engines_MyMemory' ) + ->disableOriginalConstructor() + ->onlyMethods(['update', 'getEngineRow']) + ->getMock(); $engineStruct = new EnginesModel_EngineStruct(); $engineStruct->id = 1; $engineStruct->type = 'TM'; @@ -328,7 +352,11 @@ public function testExceptionForMyMemorySetFailure() { /** * @var $queueElement Contribution\ContributionSetStruct */ - $contributionMockQueueObject = @$this->getMockBuilder( '\Contribution\ContributionSetStruct' )->getMock(); + $contributionMockQueueObject = @$this + ->getMockBuilder( '\Contribution\ContributionSetStruct' ) + ->disableOriginalConstructor() + ->onlyMethods(['getProp', 'getJobStruct']) + ->getMock(); $contributionMockQueueObject->expects( $this->once() )->method( 'getProp' ); $contributionMockQueueObject->expects( $this->once() ) diff --git a/tests/unit/TestMyMemory/TmxImportMyMemoryTest.php b/tests/unit/TestMyMemory/TmxImportMyMemoryTest.php index 6231657cbe..e9304a6100 100644 --- a/tests/unit/TestMyMemory/TmxImportMyMemoryTest.php +++ b/tests/unit/TestMyMemory/TmxImportMyMemoryTest.php @@ -35,7 +35,7 @@ public function tearDown(): void { } /** - * @covers Engines_MyMemory::import + * @covers Engines_MyMemory::importMemory * @covers Engines_MyMemory::getStatus * @covers Engines_MyMemory::createExport * @covers Engines_MyMemory::checkExport @@ -77,7 +77,7 @@ public function test_about_best_case_scenario_of_TMX_import() { /** * Importing */ - $result = $engine_MyMemory->import( $file_param, $key_param, $name_param ); + $result = $engine_MyMemory->importMemory( $file_param, $key_param, new Users_UserStruct() ); $this->assertTrue( $result instanceof Engines_Results_MyMemory_TmxResponse );