Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroupStats.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Statistics;
5
8use MediaWiki\Deferred\DeferredUpdates;
15use MediaWiki\Logger\LoggerFactory;
16use MediaWiki\MediaWikiServices;
17use MessageGroup;
18use stdClass;
19use Wikimedia\ObjectCache\WANObjectCache;
20use Wikimedia\Rdbms\Database;
21use Wikimedia\Rdbms\IDatabase;
22
36 private const TABLE = 'translate_groupstats';
38 private const LANGUAGE_STATS_KEY = 'translate-all-language-stats';
39
40 public const TOTAL = 0;
41 public const TRANSLATED = 1;
42 public const FUZZY = 2;
43 public const PROOFREAD = 3;
44
46 public const FLAG_CACHE_ONLY = 1;
48 public const FLAG_NO_CACHE = 2;
50 public const FLAG_IMMEDIATE_WRITES = 4;
51
53 private static array $updates = [];
55 private static ?array $languages = null;
56
61 public static function getEmptyStats(): array {
62 return [ 0, 0, 0, 0 ];
63 }
64
69 private static function getUnknownStats(): array {
70 return [ null, null, null, null ];
71 }
72
73 private static function isValidLanguage( string $languageCode ): bool {
74 $languages = self::getLanguages();
75 return in_array( $languageCode, $languages );
76 }
77
82 private static function isValidMessageGroup( ?MessageGroup $group ): bool {
83 return $group && !MessageGroups::isDynamic( $group );
84 }
85
93 public static function forItem( string $groupId, string $languageCode, int $flags = 0 ): array {
94 $group = MessageGroups::getGroup( $groupId );
95 if ( !self::isValidMessageGroup( $group ) || !self::isValidLanguage( $languageCode ) ) {
96 return self::getUnknownStats();
97 }
98
99 $res = self::selectRowsIdLang( [ $groupId ], [ $languageCode ], $flags );
100 $stats = self::extractResults( $res, [ $groupId ] );
101
102 if ( !isset( $stats[$groupId][$languageCode] ) ) {
103 $stats[$groupId][$languageCode] = self::forItemInternal( $stats, $group, $languageCode, $flags );
104 }
105
106 self::queueUpdates( $flags );
107
108 return $stats[$groupId][$languageCode];
109 }
110
117 public static function forLanguage( string $languageCode, int $flags = 0 ): array {
118 if ( !self::isValidLanguage( $languageCode ) ) {
119 $stats = [];
120 $groups = MessageGroups::singleton()->getGroups();
121 $ids = array_keys( $groups );
122 foreach ( $ids as $id ) {
123 $stats[$id] = self::getUnknownStats();
124 }
125
126 return $stats;
127 }
128
129 $stats = self::forLanguageInternal( $languageCode, [], $flags );
130 $flattened = [];
131 foreach ( $stats as $group => $languages ) {
132 $flattened[$group] = $languages[$languageCode];
133 }
134
135 self::queueUpdates( $flags );
136
137 return $flattened;
138 }
139
146 public static function forGroup( MessageGroup|string $group, int $flags = 0 ): array {
147 if ( !( $group instanceof MessageGroup ) ) {
148 $group = MessageGroups::getGroup( $group );
149 }
150
151 if ( !self::isValidMessageGroup( $group ) ) {
152 return array_fill_keys( self::getLanguages(), self::getUnknownStats() );
153 }
154
155 $stats = self::forGroupInternal( $group, [], $flags );
156
157 self::queueUpdates( $flags );
158
159 return $stats[$group->getId()];
160 }
161
167 public static function clear( MessageHandle $handle ): void {
168 $code = $handle->getCode();
169 if ( !self::isValidLanguage( $code ) ) {
170 return;
171 }
172 $groups = self::getSortedGroupsForClearing( $handle->getGroupIds() );
173 self::internalClearGroups( $code, $groups, 0 );
174 }
175
182 public static function clearGroup( $id, int $flags = 0 ): void {
183 $languages = self::getLanguages();
184 $groups = self::getSortedGroupsForClearing( (array)$id );
185
186 // Do one language at a time, to save memory
187 foreach ( $languages as $code ) {
188 self::internalClearGroups( $code, $groups, $flags );
189 }
190 }
191
198 public static function getApproximateLanguageStats(): array {
199 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
200 return $cache->getWithSetCallback(
201 self::LANGUAGE_STATS_KEY,
202 WANObjectCache::TTL_INDEFINITE,
203 function ( $oldValue, &$ttl, array &$setOpts ) {
204 $dbr = Utilities::getSafeReadDB();
205 $setOpts += Database::getCacheSetOptions( $dbr );
206
207 return self::getAllLanguageStats();
208 },
209 [
210 'checkKeys' => [ self::LANGUAGE_STATS_KEY ],
211 'pcTTL' => $cache::TTL_PROC_SHORT,
212 ]
213 );
214 }
215
216 private static function getAllLanguageStats(): array {
217 $dbr = Utilities::getSafeReadDB();
218 $res = $dbr->newSelectQueryBuilder()
219 ->table( self::TABLE )
220 ->select( [
221 'tgs_lang',
222 'tgs_translated' => 'SUM(tgs_translated)',
223 'tgs_fuzzy' => 'SUM(tgs_fuzzy)',
224 'tgs_total' => 'SUM(tgs_total)',
225 'tgs_proofread' => 'SUM(tgs_proofread)'
226 ] )
227 ->groupBy( 'tgs_lang' )
228 ->caller( __METHOD__ )
229 ->fetchResultSet();
230
232 $languagesCodes = array_flip( self::getLanguages() );
233
234 $allStats = [];
235 foreach ( $res as $row ) {
236 $allStats[ $row->tgs_lang ] = self::extractNumbers( $row );
237 unset( $languagesCodes[ $row->tgs_lang ] );
238 }
239
240 // Fill empty stats for missing language codes
241 foreach ( array_keys( $languagesCodes ) as $code ) {
242 $allStats[ $code ] = self::getEmptyStats();
243 }
244
245 return $allStats;
246 }
247
254 private static function internalClearGroups( string $code, array $groups, int $flags ): void {
255 $stats = [];
256 foreach ( $groups as $group ) {
257 // $stats is modified by reference
258 self::forItemInternal( $stats, $group, $code, $flags );
259 }
260 self::queueUpdates( 0 );
261 }
262
273 private static function getSortedGroupsForClearing( array $ids ): array {
274 $groups = array_map( [ MessageGroups::class, 'getGroup' ], $ids );
275 // Sanity: Remove any invalid groups
276 $groups = array_filter( $groups );
277
278 $sorted = [];
279 $aggregates = [];
280 foreach ( $groups as $group ) {
281 if ( $group instanceof AggregateMessageGroup ) {
282 $aggregates[$group->getId()] = $group;
283 } else {
284 $sorted[$group->getId()] = $group;
285 }
286 }
287
288 return array_merge( $sorted, $aggregates );
289 }
290
295 public static function getLanguages(): array {
296 if ( self::$languages === null ) {
297 $languages = array_keys( Utilities::getLanguageNames( 'en' ) );
298 sort( $languages );
299 self::$languages = $languages;
300 }
301
302 return self::$languages;
303 }
304
313 private static function extractResults( iterable $res, array $ids, array $stats = [] ): array {
314 // Map the internal ids back to real ids
315 $idMap = array_combine( array_map( [ self::class, 'getDatabaseIdForGroupId' ], $ids ), $ids );
316
317 foreach ( $res as $row ) {
318 if ( !isset( $idMap[$row->tgs_group] ) ) {
319 // Stale entry, ignore for now
320 // TODO: Schedule for purge
321 continue;
322 }
323
324 $realId = $idMap[$row->tgs_group];
325 $stats[$realId][$row->tgs_lang] = self::extractNumbers( $row );
326 }
327
328 return $stats;
329 }
330
332 private static function extractNumbers( stdClass $row ): array {
333 return [
334 self::TOTAL => (int)$row->tgs_total,
335 self::TRANSLATED => (int)$row->tgs_translated,
336 self::FUZZY => (int)$row->tgs_fuzzy,
337 self::PROOFREAD => (int)$row->tgs_proofread,
338 ];
339 }
340
347 private static function forLanguageInternal( string $languageCode, array $stats, int $flags ): array {
348 $groups = MessageGroups::singleton()->getGroups();
349
350 $ids = array_keys( $groups );
351 $res = self::selectRowsIdLang( null, [ $languageCode ], $flags );
352 $stats = self::extractResults( $res, $ids, $stats );
353
354 foreach ( $groups as $id => $group ) {
355 if ( isset( $stats[$id][$languageCode] ) ) {
356 continue;
357 }
358 $stats[$id][$languageCode] = self::forItemInternal( $stats, $group, $languageCode, $flags );
359 }
360
361 return $stats;
362 }
363
365 private static function expandAggregates( AggregateMessageGroup $agg ): array {
366 $flattened = [];
367
368 foreach ( $agg->getGroups() as $group ) {
369 if ( $group instanceof AggregateMessageGroup ) {
370 $flattened += self::expandAggregates( $group );
371 } else {
372 $flattened[$group->getId()] = $group;
373 }
374 }
375
376 return $flattened;
377 }
378
385 private static function forGroupInternal( MessageGroup $group, array $stats, int $flags ): array {
386 $id = $group->getId();
387
388 $res = self::selectRowsIdLang( [ $id ], null, $flags );
389 $stats = self::extractResults( $res, [ $id ], $stats );
390
391 // Go over each language filling missing entries
392 $languages = self::getLanguages();
393 foreach ( $languages as $code ) {
394 if ( isset( $stats[$id][$code] ) ) {
395 continue;
396 }
397 $stats[$id][$code] = self::forItemInternal( $stats, $group, $code, $flags );
398 }
399
400 // This is for sorting the values added later in correct order
401 foreach ( array_keys( $stats ) as $key ) {
402 ksort( $stats[$key] );
403 }
404
405 return $stats;
406 }
407
415 private static function selectRowsIdLang( ?array $ids, ?array $codes, int $flags ): iterable {
416 if ( $flags & self::FLAG_NO_CACHE ) {
417 return [];
418 }
419
420 $conditions = [];
421 if ( $ids !== null ) {
422 $dbids = array_map( [ self::class, 'getDatabaseIdForGroupId' ], $ids );
423 $conditions['tgs_group'] = $dbids;
424 }
425
426 if ( $codes !== null ) {
427 $conditions['tgs_lang'] = $codes;
428 }
429
430 $dbr = Utilities::getSafeReadDB();
431 return $dbr->newSelectQueryBuilder()
432 ->select( '*' )
433 ->from( self::TABLE )
434 ->where( $conditions )
435 ->caller( __METHOD__ )
436 ->fetchResultSet();
437 }
438
446 private static function forItemInternal(
447 array &$stats,
448 MessageGroup $group,
449 string $languageCode,
450 int $flags
451 ): array {
452 $id = $group->getId();
453
454 if ( $flags & self::FLAG_CACHE_ONLY ) {
455 $stats[$id][$languageCode] = self::getUnknownStats();
456 return $stats[$id][$languageCode];
457 }
458
459 // It may happen that caches are requested repeatedly for a group before we get a chance
460 // to write the values to the database. Check for queued updates first. This has the
461 // benefit of avoiding duplicate rows for inserts. Ideally this would be checked before we
462 // query the database for missing values. This code is somewhat ugly as it needs to
463 // reverse engineer the values from the row format.
464 $databaseGroupId = self::getDatabaseIdForGroupId( $id );
465 $uniqueKey = "$databaseGroupId|$languageCode";
466 $queuedValue = self::$updates[$uniqueKey] ?? null;
467 if ( $queuedValue && !( $flags & self::FLAG_NO_CACHE ) ) {
468 return [
469 self::TOTAL => $queuedValue['tgs_total'],
470 self::TRANSLATED => $queuedValue['tgs_translated'],
471 self::FUZZY => $queuedValue['tgs_fuzzy'],
472 self::PROOFREAD => $queuedValue['tgs_proofread'],
473 ];
474 }
475
476 if ( $group instanceof AggregateMessageGroup ) {
477 $aggregates = self::calculateAggregateGroup( $stats, $group, $languageCode, $flags );
478 } else {
479 $aggregates = self::calculateGroup( $group, $languageCode );
480 }
481 // Cache for use in subsequent forItemInternal calls
482 $stats[$id][$languageCode] = $aggregates;
483
484 // Don't add nulls to the database, causes annoying warnings
485 if ( $aggregates[self::TOTAL] === null ) {
486 return $aggregates;
487 }
488
489 self::$updates[$uniqueKey] = [
490 'tgs_group' => $databaseGroupId,
491 'tgs_lang' => $languageCode,
492 'tgs_total' => $aggregates[self::TOTAL],
493 'tgs_translated' => $aggregates[self::TRANSLATED],
494 'tgs_fuzzy' => $aggregates[self::FUZZY],
495 'tgs_proofread' => $aggregates[self::PROOFREAD],
496 ];
497
498 // For big and lengthy updates, attempt some interim saves. This might not have
499 // any effect, because writes to the database may be deferred.
500 if ( count( self::$updates ) % 100 === 0 ) {
501 self::queueUpdates( $flags );
502 }
503
504 return $aggregates;
505 }
506
507 private static function calculateAggregateGroup(
508 array &$stats,
510 string $code,
511 int $flags
512 ): array {
513 $aggregates = self::getEmptyStats();
514
515 $expanded = self::expandAggregates( $group );
516 $subGroupIds = array_keys( $expanded );
517
518 // Performance: if we have per-call cache of stats, do not query them again.
519 foreach ( $subGroupIds as $index => $sid ) {
520 if ( isset( $stats[$sid][$code] ) ) {
521 unset( $subGroupIds[ $index ] );
522 }
523 }
524
525 if ( $subGroupIds !== [] ) {
526 $res = self::selectRowsIdLang( $subGroupIds, [ $code ], $flags );
527 $stats = self::extractResults( $res, $subGroupIds, $stats );
528 }
529
530 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
531 foreach ( $expanded as $sid => $subgroup ) {
532 // Discouraged groups may belong to another group, usually if there
533 // is an aggregate group for all translatable pages. In that case
534 // calculate and store the statistics, but don't count them as part of
535 // the aggregate group, so that the numbers in Special:LanguageStats
536 // add up. The statistics for discouraged groups can still be viewed
537 // through Special:MessageGroupStats.
538 if ( !isset( $stats[$sid][$code] ) ) {
539 $stats[$sid][$code] = self::forItemInternal( $stats, $subgroup, $code, $flags );
540 }
541
542 if ( !$messageGroupMetadata->isExcluded( $sid, $code ) ) {
543 $aggregates = self::multiAdd( $aggregates, $stats[$sid][$code] );
544 }
545 }
546
547 return $aggregates;
548 }
549
550 public static function multiAdd( array $a, array $b ): array {
551 if ( $a[0] === null || $b[0] === null ) {
552 return array_fill( 0, count( $a ), null );
553 }
554 foreach ( $a as $i => &$v ) {
555 $v += $b[$i];
556 }
557
558 return $a;
559 }
560
566 private static function calculateGroup( MessageGroup $group, string $languageCode ): array {
567 global $wgTranslateDocumentationLanguageCode;
568 // Calculate if missing and store in the db
569 $collection = $group->initCollection( $languageCode );
570
571 if (
572 $languageCode === $wgTranslateDocumentationLanguageCode
573 && $group instanceof FileBasedMessageGroup
574 ) {
575 $cache = $group->getMessageGroupCache( $group->getSourceLanguage() );
576 if ( $cache->exists() ) {
577 $template = $cache->getExtra()['TEMPLATE'] ?? [];
578 $infile = [];
579 foreach ( $template as $key => $data ) {
580 if ( isset( $data['comments']['.'] ) ) {
581 $infile[$key] = '1';
582 }
583 }
584 $collection->setInFile( $infile );
585 }
586 }
587
588 return self::getStatsForCollection( $collection );
589 }
590
591 private static function queueUpdates( int $flags ): void {
592 $mwInstance = MediaWikiServices::getInstance();
593 if ( self::$updates === [] || $mwInstance->getReadOnlyMode()->isReadOnly() ) {
594 return;
595 }
596
597 $dbw = $mwInstance->getConnectionProvider()->getPrimaryDatabase(); // avoid connecting yet
598 $callers = wfGetAllCallers( 50 );
599 $functionName = __METHOD__;
600 $callback = static function ( IDatabase $dbw, $method ) use ( $callers, $mwInstance ) {
601 // This path should only be hit during web requests
602 if ( count( self::$updates ) > 100 ) {
603 $groups = array_unique( array_column( self::$updates, 'tgs_group' ) );
604 LoggerFactory::getInstance( LogNames::MAIN )->warning(
605 "Huge translation update of {count} rows for group(s) {groups}",
606 [
607 'count' => count( self::$updates ),
608 'groups' => implode( ', ', $groups ),
609 'callers' => $callers,
610 ]
611 );
612 }
613
614 $dbw->newReplaceQueryBuilder()
615 ->replaceInto( self::TABLE )
616 ->uniqueIndexFields( [ 'tgs_group', 'tgs_lang' ] )
617 ->rows( array_values( self::$updates ) )
618 ->caller( $method )
619 ->execute();
620 self::$updates = [];
621
622 $mwInstance->getMainWANObjectCache()->touchCheckKey( self::LANGUAGE_STATS_KEY );
623 };
624 $updateOp = static function () use ( $dbw, $functionName, $callback ) {
625 // Maybe another deferred update already processed these
626 if ( self::$updates === [] ) {
627 return;
628 }
629
630 $lockName = 'MessageGroupStats:updates';
631 if ( !$dbw->lock( $lockName, $functionName, 1 ) ) {
632 $groups = array_unique( array_column( self::$updates, 'tgs_group' ) );
633 LoggerFactory::getInstance( LogNames::MAIN )->warning(
634 'Message group stats update of {count} rows failed for group(s) {groups} due to lock',
635 [
636 'count' => count( self::$updates ),
637 'groups' => implode( ', ', $groups ),
638 ]
639 );
640
641 return; // raced out
642 }
643
644 $dbw->commit( $functionName, 'flush' );
645 $callback( $dbw, $functionName );
646 $dbw->commit( $functionName, 'flush' );
647
648 $dbw->unlock( $lockName, $functionName );
649 };
650
651 if ( $flags & self::FLAG_IMMEDIATE_WRITES ) {
652 $updateOp();
653 } else {
654 DeferredUpdates::addCallableUpdate( $updateOp );
655 }
656 }
657
658 public static function getDatabaseIdForGroupId( string $id ): string {
659 // The column is 100 bytes long, but we don't need to use it all
660 if ( strlen( $id ) <= 72 ) {
661 return $id;
662 }
663
664 $hash = hash( 'sha256', $id, /*asHex*/false );
665 return substr( $id, 0, 50 ) . '||' . substr( $hash, 0, 20 );
666 }
667
669 public static function getStatsForCollection( MessageCollection $collection ): array {
670 $collection->filter( MessageCollection::FILTER_IGNORED, MessageCollection::EXCLUDE_MATCHING );
671 $collection->filterUntranslatedOptional();
672 // Store the count of real messages for later calculation.
673 $total = count( $collection );
674
675 // Count fuzzy first.
676 $collection->filter( MessageCollection::FILTER_FUZZY, MessageCollection::EXCLUDE_MATCHING );
677 $fuzzy = $total - count( $collection );
678
679 // Count the completed translations.
680 $collection->filter( MessageCollection::FILTER_HAS_TRANSLATION, MessageCollection::INCLUDE_MATCHING );
681 $translated = count( $collection );
682
683 // Count how many of the completed translations
684 // have been proofread
685 $collection->filter( MessageCollection::FILTER_REVIEWER, MessageCollection::INCLUDE_MATCHING );
686 $proofread = count( $collection );
687
688 return [
689 self::TOTAL => $total,
690 self::TRANSLATED => $translated,
691 self::FUZZY => $fuzzy,
692 self::PROOFREAD => $proofread,
693 ];
694 }
695}
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager($services->getTitleFactory(), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:AggregateGroupMessageGroupFactory'=> static function(MediaWikiServices $services):AggregateGroupMessageGroupFactory { return new AggregateGroupMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'));}, '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:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), $services->getContentLanguageCode() ->toString(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, '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:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance(LogNames::GROUP_SUBSCRIPTION), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):?MessageGroupSubscriptionHookHandler { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, '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->getConnectionProvider(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getConnectionProvider());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getConnectionProvider());}, '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(), $services->getNamespaceInfo(), $services->getTitleFactory(), $services->getFormatterFactory());}, '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->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory(), $services->get( 'Translate:HookRunner'),);}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, '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->getConnectionProvider());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);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@phpcs-require-sorted-array
Groups multiple message groups together as one group.
getGroups()
Returns a list of message groups that this group consists of.
This class implements default behavior for file based message groups.
Constants for log channel names used in this extension.
Definition LogNames.php:13
Factory class for accessing message groups individually by id or all of them as a list.
This file contains the class for core message collections implementation.
filter(string $filter, bool $condition, ?int $value=null)
Filters messages based on some condition.
Class for pointing to messages, like Title class is for titles.
getGroupIds()
Returns all message group ids this message belongs to.
Minimal service container.
Definition Services.php:60
This class aims to provide efficient mechanism for fetching translation completion stats.
static clearGroup( $id, int $flags=0)
Recalculate stats for given group(s).
static clear(MessageHandle $handle)
Recalculate stats for all groups associated with the message.
static getStatsForCollection(MessageCollection $collection)
static getLanguages()
Get list of supported languages for statistics.
static forItem(string $groupId, string $languageCode, int $flags=0)
Returns stats for given group in given language.
static forLanguage(string $languageCode, int $flags=0)
Returns stats for all groups in given language.
const FLAG_CACHE_ONLY
If stats are not cached, do not attempt to calculate them on the fly.
static getApproximateLanguageStats()
Fetch aggregated statistics for all languages across groups.
static forGroup(MessageGroup|string $group, int $flags=0)
Returns stats for all languages in given group.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:30
Interface for message groups.
initCollection( $code)
Initialises a message collection with the given language code, message definitions and message tags.
getSourceLanguage()
Returns language code depicting the language of source text.