11use Elastica\Aggregation\Terms;
14use Elastica\Exception\ExceptionInterface;
16use Elastica\Query\BoolQuery;
17use Elastica\Query\FunctionScore;
18use Elastica\Query\MatchQuery;
19use Elastica\Query\Term;
20use Elastica\ResultSet;
21use MediaWiki\Extension\Elastica\MWElasticUtils;
26use MediaWiki\Logger\LoggerFactory;
27use MediaWiki\MediaWikiServices;
43 private const BULK_INDEX_RETRY_ATTEMPTS = 5;
50 private const WAIT_UNTIL_READY_TIMEOUT = 3600;
64 return $suggestion[
'wiki'] === WikiMap::getCurrentWikiId();
71 public function query(
string $sourceLanguage,
string $targetLanguage,
string $text ): array {
73 return $this->doQuery( $sourceLanguage, $targetLanguage, $text );
74 }
catch ( Exception $e ) {
79 protected function doQuery( $sourceLanguage, $targetLanguage, $text ) {
80 if ( !$this->useWikimediaExtraPlugin() ) {
83 throw new RuntimeException(
'The wikimedia extra plugin is mandatory.' );
89 $connection = $this->getClient()->getConnection();
90 $oldTimeout = $connection->getTimeout();
91 $connection->setTimeout( 10 );
94 $fuzzyQuery->setLikeText( $text );
95 $fuzzyQuery->addFields( [
'content' ] );
97 $boostQuery =
new FunctionScore();
98 $boostQuery->addFunction(
99 'levenshtein_distance_score',
105 $boostQuery->setBoostMode( FunctionScore::BOOST_MODE_REPLACE );
109 $bool =
new BoolQuery();
110 $bool->addFilter( $fuzzyQuery );
111 $bool->addMust( $boostQuery );
113 $languageFilter =
new Term();
114 $languageFilter->setTerm(
'language', $sourceLanguage );
115 $bool->addFilter( $languageFilter );
118 $query =
new Query();
119 $query->setQuery( $bool );
130 $sizeSecond = $sizeFirst * 5;
132 $query->setFrom( 0 );
133 $query->setSize( $sizeFirst );
134 $query->setParam(
'_source', [
'content' ] );
135 $cutoff = $this->config[
'cutoff'] ?? 0.65;
136 $query->setParam(
'min_score', $cutoff );
137 $query->setSort( [
'_score',
'wiki',
'localid' ] );
144 $contents = $scores = $terms = [];
146 $resultset = $this->getIndex()->search( $query );
148 if ( count( $resultset ) === 0 ) {
152 foreach ( $resultset->getResults() as $result ) {
153 $data = $result->getData();
154 $score = $result->getScore();
156 $sourceId = preg_replace(
'~/[^/]+$~',
'', $result->getId() );
157 $contents[$sourceId] = $data[
'content'];
158 $scores[$sourceId] = $score;
159 $terms[] =
"$sourceId/$targetLanguage";
166 if ( count( array_unique( $scores ) ) > 5 ) {
173 if ( count( $resultset ) === $sizeSecond ) {
179 $query->setParam(
'min_score', $score );
180 $query->setFrom( $query->getParam(
'size' ) + $query->getParam(
'from' ) );
181 $query->setSize( $sizeSecond );
184 }
while ( $resultset->getTotalHits() > count( $contents ) );
190 if ( $terms !== [] ) {
191 $idQuery =
new Query\Terms(
'_id', $terms );
193 $query =
new Query( $idQuery );
194 $query->setSize( 25 );
195 $query->setParam(
'_source', [
'wiki',
'uri',
'content',
'localid' ] );
196 $resultset = $this->getIndex()->search( $query );
198 foreach ( $resultset->getResults() as $result ) {
199 $data = $result->getData();
202 $sourceId = preg_replace(
'~/[^/]+$~',
'', $result->getId() );
205 'source' => $contents[$sourceId],
206 'target' => $data[
'content'],
207 'context' => $data[
'localid'],
208 'quality' => $scores[$sourceId],
209 'wiki' => $data[
'wiki'],
210 'location' => $data[
'localid'] .
'/' . $targetLanguage,
211 'uri' => $data[
'uri'],
216 uasort( $suggestions,
static function ( $a, $b ) {
217 if ( $a[
'quality'] === $b[
'quality'] ) {
221 return ( $a[
'quality'] < $b[
'quality'] ) ? 1 : -1;
225 $connection->setTimeout( $oldTimeout );
233 if ( !$handle->isValid() || $handle->getCode() ===
'' ) {
247 $sourceLanguage = $handle->getGroup()->getSourceLanguage();
250 if ( $handle->getCode() !== $sourceLanguage ) {
251 $localid = $handle->getTitleForBase()->getPrefixedText();
252 $this->deleteByQuery( $this->getIndex(), Query::create(
254 ->addFilter(
new Term( [
'wiki' => WikiMap::getCurrentWikiId() ] ) )
255 ->addFilter(
new Term( [
'language' => $handle->getCode() ] ) )
256 ->addFilter(
new Term( [
'localid' => $localid ] ) ) ) );
260 if ( $targetText ===
null ) {
265 if ( $sourceLanguage ===
null ) {
269 $revId = $handle->getTitleForLanguage( $sourceLanguage )->getLatestRevID();
270 $doc = $this->createDocument( $handle, $targetText, $revId );
273 MWElasticUtils::withRetry( self::BULK_INDEX_RETRY_ATTEMPTS,
274 function () use ( $doc ) {
275 $this->getIndex()->addDocuments( [ $doc ] );
277 static function ( $e, $errors ) use ( $fname ) {
278 $c = get_class( $e );
279 $msg = $e->getMessage();
280 error_log( $fname .
": update failed ($c: $msg); retrying." );
295 $language = $handle->
getCode();
298 $wiki = WikiMap::getCurrentWikiId();
299 $globalid =
"$wiki-$localid-$revId/$language";
303 'uri' => $handle->
getTitle()->getCanonicalURL(),
304 'localid' => $localid,
305 'language' => $language,
310 return new Document( $globalid, $data,
'_doc' );
321 'number_of_shards' => $this->getShardCount(),
325 'type' =>
'edge_ngram',
333 'tokenizer' =>
'standard',
334 'filter' => [
'lowercase',
'prefix_filter' ]
337 'tokenizer' =>
'standard'
344 $replicas = $this->getReplicaCount();
345 $key = str_contains( $replicas,
'-' ) ?
'auto_expand_replicas' :
'number_of_replicas';
346 $indexSettings[
'settings'][
'index'][$key] = $replicas;
348 $this->getIndex()->create( $indexSettings, $rebuild );
357 $this->checkElasticsearchVersion();
358 $index = $this->getIndex();
359 if ( $this->updateMapping ) {
360 $this->logOutput(
'Updating the index mappings...' );
361 $this->createIndex(
true );
362 } elseif ( !$index->exists() ) {
363 $this->createIndex(
false );
366 $settings = $index->getSettings();
367 $settings->setRefreshInterval(
'-1' );
369 $this->deleteByQuery( $this->getIndex(), Query::create(
370 (
new Term() )->setTerm(
'wiki', WikiMap::getCurrentWikiId() ) ) );
373 'wiki' => [
'type' =>
'keyword' ],
374 'localid' => [
'type' =>
'keyword' ],
375 'uri' => [
'type' =>
'keyword' ],
376 'language' => [
'type' =>
'keyword' ],
377 'group' => [
'type' =>
'keyword' ],
383 'term_vector' =>
'yes'
385 'prefix_complete' => [
387 'analyzer' =>
'prefix',
388 'search_analyzer' =>
'standard',
389 'term_vector' =>
'yes'
391 'case_sensitive' => [
393 'analyzer' =>
'casesensitive',
394 'term_vector' =>
'yes'
399 if ( $this->useElastica6() ) {
402 $mapping = new \Elastica\Type\Mapping();
404 $mapping->setType( $index->getType(
'_doc' ) );
406 $mapping->setProperties( $properties );
408 $mapping->send( [
'include_type_name' =>
'true' ] );
411 $mapping = new \Elastica\Mapping( $properties );
412 $mapping->send( $index, [
'include_type_name' =>
'false' ] );
415 $this->waitUntilReady();
425 public function batchInsertDefinitions( array $batch ): void {
426 $lb = MediaWikiServices::getInstance()->getLinkBatchFactory()->newLinkBatch();
427 foreach ( $batch as $data ) {
428 $lb->addObj( $data[0]->getTitle() );
432 $this->batchInsertTranslations( $batch );
437 foreach ( $batch as $data ) {
438 [ $handle, $sourceLanguage, $text ] = $data;
439 $revId = $handle->getTitleForLanguage( $sourceLanguage )->getLatestRevID();
440 $docs[] = $this->createDocument( $handle, $text, $revId );
443 MWElasticUtils::withRetry( self::BULK_INDEX_RETRY_ATTEMPTS,
444 function () use ( $docs ) {
445 $this->getIndex()->addDocuments( $docs );
447 function ( $e, $errors ) {
448 $c = get_class( $e );
449 $msg = $e->getMessage();
450 $this->logOutput(
"Batch failed ($c: $msg), trying again in 10 seconds" );
459 public function endBootstrap(): void {
460 $index = $this->getIndex();
462 $index->forcemerge();
463 $index->getSettings()->setRefreshInterval(
'5s' );
466 public function getClient() {
467 if ( !$this->client ) {
468 if ( isset( $this->config[
'config'] ) ) {
469 $this->client =
new Client( $this->config[
'config'] );
471 $this->client =
new Client();
474 return $this->client;
479 return isset( $this->config[
'use_wikimedia_extra'] ) && $this->config[
'use_wikimedia_extra'];
483 private function getIndexName() {
484 return $this->config[
'index'] ??
'ttmserver';
487 public function getIndex() {
488 return $this->getClient()
489 ->getIndex( $this->getIndexName() );
492 protected function getShardCount() {
493 return $this->config[
'shards'] ?? 1;
496 protected function getReplicaCount() {
497 return $this->config[
'replicas'] ??
'0-2';
509 $path =
"_cluster/health/$indexName";
510 $response = $this->getClient()->request( $path );
511 if ( $response->hasError() ) {
512 throw new Exception(
"Error while fetching index health status: " . $response->getError() );
514 return $response->getData();
533 while ( ( $startTime + $timeout ) > time() ) {
535 $response = $this->getIndexHealth( $indexName );
536 $status = $response[
'status'] ??
'unknown';
537 if ( $status ===
'green' ) {
538 $this->logOutput(
"\tGreen!" );
541 $this->logOutput(
"\tIndex is $status retrying..." );
543 }
catch ( Exception $e ) {
544 $this->logOutput(
"Error while waiting for green ({$e->getMessage()}), retrying..." );
550 protected function waitUntilReady() {
551 $statuses = MWElasticUtils::waitForGreen(
553 $this->getIndexName(),
554 self::WAIT_UNTIL_READY_TIMEOUT );
555 $this->logOutput(
"Waiting for the index to go green..." );
556 foreach ( $statuses as $message ) {
557 $this->logOutput( $message );
560 if ( !$statuses->getReturn() ) {
561 die(
"Timeout! Please check server logs for {$this->getIndexName()}." );
565 public function setLogger( $logger ) {
566 $this->logger = $logger;
570 protected function logOutput( $text ) {
571 if ( $this->logger ) {
572 $this->logger->statusLine(
"$text\n" );
577 $this->updateMapping = true;
587 $fields = $highlights = [];
588 $terms = preg_split(
'/\s+/', $queryString );
589 $match = $opts[
'match'];
590 $case = $opts[
'case'];
593 foreach ( $terms as $term ) {
594 $prefix = strstr( $term,
'*',
true );
597 $fields[
'content.prefix_complete'][] = $prefix;
598 } elseif ( $case ===
'1' ) {
600 $fields[
'content.case_sensitive'][] = $term;
602 $fields[
'content'][] = $term;
608 $searchQuery =
new BoolQuery();
609 foreach ( $fields as $analyzer => $words ) {
610 foreach ( $words as $word ) {
611 $boolQuery =
new BoolQuery();
612 $contentQuery =
new MatchQuery();
613 $contentQuery->setFieldQuery( $analyzer, $word );
614 $boolQuery->addShould( $contentQuery );
615 $messageQuery =
new Term();
616 $messageQuery->setTerm(
'localid', $word );
617 $boolQuery->addShould( $messageQuery );
619 if ( $match ===
'all' ) {
620 $searchQuery->addMust( $boolQuery );
622 $searchQuery->addShould( $boolQuery );
626 $highlights[$analyzer] = [
627 'number_of_fragments' => 0
632 $title = Title::newFromText( $word );
637 if ( $handle->isValid() && $handle->getCode() !==
'' ) {
638 $localid = $handle->getTitleForBase()->getPrefixedText();
639 $boolQuery =
new BoolQuery();
640 $messageId =
new Term();
641 $messageId->setTerm(
'localid', $localid );
642 $boolQuery->addMust( $messageId );
643 $searchQuery->addShould( $boolQuery );
648 return [ $searchQuery, $highlights ];
659 $query =
new Query();
661 [ $searchQuery, $highlights ] = $this->parseQueryString( $queryString, $opts );
662 $query->setQuery( $searchQuery );
664 $language =
new Terms(
'language' );
665 $language->setField(
'language' );
666 $language->setSize( 500 );
667 $query->addAggregation( $language );
669 $group =
new Terms(
'group' );
670 $group->setField(
'group' );
673 $group->setSize( 500 );
674 $query->addAggregation( $group );
676 $query->setSize( $opts[
'limit'] );
677 $query->setFrom( $opts[
'offset'] );
683 $filters =
new BoolQuery();
685 $language = $opts[
'language'];
686 if ( $language !==
'' ) {
687 $languageFilter =
new Term();
688 $languageFilter->setTerm(
'language', $language );
689 $filters->addFilter( $languageFilter );
692 $group = $opts[
'group'];
693 if ( $group !==
'' ) {
694 $groupFilter =
new Term();
695 $groupFilter->setTerm(
'group', $group );
696 $filters->addFilter( $groupFilter );
700 if ( $language !==
'' || $group !==
'' ) {
704 $query->setPostFilter( $filters );
707 [ $pre, $post ] = $highlight;
708 $query->setHighlight( [
710 'pre_tags' => [ $pre ],
711 'post_tags' => [ $post ],
712 'fields' => $highlights,
715 return $this->getIndex()->createSearch( $query );
726 public function search( $queryString, $opts, $highlight ) {
727 $search = $this->createSearch( $queryString, $opts, $highlight );
730 return $search->search();
731 }
catch ( ExceptionInterface $e ) {
738 $this->assertResultSetInstance( $resultset );
739 $aggs = $resultset->getAggregations();
740 '@phan-var array[][][] $aggs';
747 foreach ( $aggs as $type => $info ) {
748 foreach ( $info[
'buckets'] as $row ) {
749 $ret[$type][$row[
'key']] = $row[
'doc_count'];
758 $this->assertResultSetInstance( $resultset );
759 return $resultset->getTotalHits();
764 $this->assertResultSetInstance( $resultset );
766 foreach ( $resultset->getResults() as $document ) {
767 $data = $document->getData();
768 $hl = $document->getHighlights();
769 if ( isset( $hl[
'content.prefix_complete'][0] ) ) {
770 $data[
'content'] = $hl[
'content.prefix_complete'][0];
771 } elseif ( isset( $hl[
'content.case_sensitive'][0] ) ) {
772 $data[
'content'] = $hl[
'content.case_sensitive'][0];
773 } elseif ( isset( $hl[
'content'][0] ) ) {
774 $data[
'content'] = $hl[
'content'][0];
791 private function deleteByQuery( \Elastica\Index $index, Query $query ) {
793 MWElasticUtils::deleteByQuery( $index, $query,
true );
794 }
catch ( Exception $e ) {
795 LoggerFactory::getInstance(
'ElasticSearchTTMServer' )->error(
796 'Problem encountered during deletion.',
797 [
'exception' => $e ]
800 throw new RuntimeException(
"Problem encountered during deletion.\n" . $e );
805 private function getElasticsearchVersion(): string {
806 $response = $this->getClient()->request(
'' );
807 if ( !$response->isOK() ) {
808 throw new \RuntimeException(
"Cannot fetch elasticsearch version: " . $response->getError() );
811 $result = $response->getData();
812 if ( !isset( $result[
'version'][
'number'] ) ) {
813 throw new \RuntimeException(
'Unable to determine elasticsearch version, aborting.' );
816 return $result[
'version' ][
'number' ];
819 private function checkElasticsearchVersion() {
820 $version = $this->getElasticsearchVersion();
821 if ( !str_starts_with( $version,
'6.8' ) && !str_starts_with( $version,
'7.' ) ) {
822 throw new \RuntimeException(
"Only Elasticsearch 6.8.x and 7.x are supported. Your version: $version." );
826 private function useElastica6(): bool {
827 return class_exists(
'\Elastica\Type' );
830 private function assertResultSetInstance( $resultset ): void {
831 if ( $resultset instanceof ResultSet ) {
835 throw new RuntimeException(
836 "Expected resultset to be an instance of " . ResultSet::class
return[ 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'));}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getDBLoadBalancer(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore($services->getDBLoadBalancerFactory());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getDBLoadBalancer());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup());}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'),);}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnection(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getDBLoadBalancer());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
TTMServer backed based on ElasticSearch.
createDocument(MessageHandle $handle, $text, $revId)
batchInsertTranslations(array $batch)
Called multiple times per batch if necessary.
$logger
Reference to the maintenance script to relay logging output.
getFacets( $resultset)
@inheritDoc
endBatch()
Called after every batch (MessageGroup).
waitForGreen( $indexName, $timeout)
Wait for the index to go green.
search( $queryString, $opts, $highlight)
Search interface.
update(MessageHandle $handle, ?string $targetText)
Shovels the new translation into translation memory.
beginBootstrap()
Begin the bootstrap process.
createIndex( $rebuild)
Create index.
getTotalHits( $resultset)
@inheritDoc
getDocuments( $resultset)
@inheritDoc
useWikimediaExtraPlugin()
getIndexHealth( $indexName)
Get index health TODO: Remove this code in the future as we drop support for older versions of the El...
beginBatch()
Called before every batch (MessageGroup).
createSearch( $queryString, $opts, $highlight)
Search interface.
setDoReIndex()
Instruct the service to fully wipe the index and start from scratch.
query(string $sourceLanguage, string $targetLanguage, string $text)
Fetches all relevant suggestions for given text.
parseQueryString( $queryString, array $opts)
Parse query string and build the search query.
isLocalSuggestion(array $suggestion)
Determines if the suggestion returned by this TTMServer comes from this wiki or any other wiki.
expandLocation(array $suggestion)
Given suggestion returned by this TTMServer, constructs fully qualified URL to the location of the tr...
$updateMapping
Used for Reindex.
Class for pointing to messages, like Title class is for titles.
getGroupIds()
Returns all message group ids this message belongs to.
getTitle()
Get the original title.
getCode()
Returns the language code.
getTitleForBase()
Get the title for the page base.
Some general static methods for instantiating TTMServer and helpers.