Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroupStats.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Statistics;
5
7use DeferredUpdates;
14use MediaWiki\Logger\LoggerFactory;
15use MediaWiki\MediaWikiServices;
16use MessageGroup;
17use stdClass;
18use WANObjectCache;
19use Wikimedia\Rdbms\Database;
20use Wikimedia\Rdbms\IDatabase;
21
35 private const TABLE = 'translate_groupstats';
37 private const LANGUAGE_STATS_KEY = 'translate-all-language-stats';
38
39 public const TOTAL = 0;
40 public const TRANSLATED = 1;
41 public const FUZZY = 2;
42 public const PROOFREAD = 3;
43
45 public const FLAG_CACHE_ONLY = 1;
47 public const FLAG_NO_CACHE = 2;
49 public const FLAG_IMMEDIATE_WRITES = 4;
50
52 private static array $updates = [];
54 private static ?array $languages = null;
55
60 public static function getEmptyStats(): array {
61 return [ 0, 0, 0, 0 ];
62 }
63
68 private static function getUnknownStats(): array {
69 return [ null, null, null, null ];
70 }
71
72 private static function isValidLanguage( string $languageCode ): bool {
73 $languages = self::getLanguages();
74 return in_array( $languageCode, $languages );
75 }
76
81 private static function isValidMessageGroup( ?MessageGroup $group ): bool {
82 return $group && !MessageGroups::isDynamic( $group );
83 }
84
92 public static function forItem( string $groupId, string $languageCode, int $flags = 0 ): array {
93 $group = MessageGroups::getGroup( $groupId );
94 if ( !self::isValidMessageGroup( $group ) || !self::isValidLanguage( $languageCode ) ) {
95 return self::getUnknownStats();
96 }
97
98 $res = self::selectRowsIdLang( [ $groupId ], [ $languageCode ], $flags );
99 $stats = self::extractResults( $res, [ $groupId ] );
100
101 if ( !isset( $stats[$groupId][$languageCode] ) ) {
102 $stats[$groupId][$languageCode] = self::forItemInternal( $stats, $group, $languageCode, $flags );
103 }
104
105 self::queueUpdates( $flags );
106
107 return $stats[$groupId][$languageCode];
108 }
109
116 public static function forLanguage( string $languageCode, int $flags = 0 ): array {
117 if ( !self::isValidLanguage( $languageCode ) ) {
118 $stats = [];
119 $groups = MessageGroups::singleton()->getGroups();
120 $ids = array_keys( $groups );
121 foreach ( $ids as $id ) {
122 $stats[$id] = self::getUnknownStats();
123 }
124
125 return $stats;
126 }
127
128 $stats = self::forLanguageInternal( $languageCode, [], $flags );
129 $flattened = [];
130 foreach ( $stats as $group => $languages ) {
131 $flattened[$group] = $languages[$languageCode];
132 }
133
134 self::queueUpdates( $flags );
135
136 return $flattened;
137 }
138
145 public static function forGroup( string $groupId, int $flags = 0 ): array {
146 $group = MessageGroups::getGroup( $groupId );
147 if ( !self::isValidMessageGroup( $group ) ) {
148 $languages = self::getLanguages();
149 $stats = [];
150 foreach ( $languages as $code ) {
151 $stats[$code] = self::getUnknownStats();
152 }
153
154 return $stats;
155 }
156
157 $stats = self::forGroupInternal( $group, [], $flags );
158
159 self::queueUpdates( $flags );
160
161 return $stats[$groupId];
162 }
163
170 public static function clear( MessageHandle $handle ): void {
171 $code = $handle->getCode();
172 if ( !self::isValidLanguage( $code ) ) {
173 return;
174 }
175 $groups = self::getSortedGroupsForClearing( $handle->getGroupIds() );
176 self::internalClearGroups( $code, $groups, 0 );
177 }
178
185 public static function clearGroup( $id, int $flags = 0 ): void {
186 $languages = self::getLanguages();
187 $groups = self::getSortedGroupsForClearing( (array)$id );
188
189 // Do one language at a time, to save memory
190 foreach ( $languages as $code ) {
191 self::internalClearGroups( $code, $groups, $flags );
192 }
193 }
194
201 public static function getApproximateLanguageStats(): array {
202 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
203 return $cache->getWithSetCallback(
204 self::LANGUAGE_STATS_KEY,
205 WANObjectCache::TTL_INDEFINITE,
206 function ( $oldValue, &$ttl, array &$setOpts ) {
207 $dbr = Utilities::getSafeReadDB();
208 $setOpts += Database::getCacheSetOptions( $dbr );
209
210 return self::getAllLanguageStats();
211 },
212 [
213 'checkKeys' => [ self::LANGUAGE_STATS_KEY ],
214 'pcTTL' => $cache::TTL_PROC_SHORT,
215 ]
216 );
217 }
218
219 private static function getAllLanguageStats(): array {
220 $dbr = Utilities::getSafeReadDB();
221 $res = $dbr->newSelectQueryBuilder()
222 ->table( self::TABLE )
223 ->select( [
224 'tgs_lang',
225 'tgs_translated' => 'SUM(tgs_translated)',
226 'tgs_fuzzy' => 'SUM(tgs_fuzzy)',
227 'tgs_total' => 'SUM(tgs_total)',
228 'tgs_proofread' => 'SUM(tgs_proofread)'
229 ] )
230 ->groupBy( 'tgs_lang' )
231 ->caller( __METHOD__ )
232 ->fetchResultSet();
233
234 $allLanguages = self::getLanguages();
235 $languagesCodes = array_flip( $allLanguages );
236
237 $allStats = [];
238 foreach ( $res as $row ) {
239 $allStats[ $row->tgs_lang ] = self::extractNumbers( $row );
240 unset( $languagesCodes[ $row->tgs_lang ] );
241 }
242
243 // Fill empty stats for missing language codes
244 foreach ( array_keys( $languagesCodes ) as $code ) {
245 $allStats[ $code ] = self::getEmptyStats();
246 }
247
248 return $allStats;
249 }
250
257 private static function internalClearGroups( string $code, array $groups, int $flags ): void {
258 $stats = [];
259 foreach ( $groups as $group ) {
260 // $stats is modified by reference
261 self::forItemInternal( $stats, $group, $code, $flags );
262 }
263 self::queueUpdates( 0 );
264 }
265
276 private static function getSortedGroupsForClearing( array $ids ): array {
277 $groups = array_map( [ MessageGroups::class, 'getGroup' ], $ids );
278 // Sanity: Remove any invalid groups
279 $groups = array_filter( $groups );
280
281 $sorted = [];
282 $aggregates = [];
283 foreach ( $groups as $group ) {
284 if ( $group instanceof AggregateMessageGroup ) {
285 $aggregates[$group->getId()] = $group;
286 } else {
287 $sorted[$group->getId()] = $group;
288 }
289 }
290
291 return array_merge( $sorted, $aggregates );
292 }
293
298 public static function getLanguages(): array {
299 if ( self::$languages === null ) {
300 $languages = array_keys( Utilities::getLanguageNames( 'en' ) );
301 sort( $languages );
302 self::$languages = $languages;
303 }
304
305 return self::$languages;
306 }
307
316 private static function extractResults( iterable $res, array $ids, array $stats = [] ): array {
317 // Map the internal ids back to real ids
318 $idMap = array_combine( array_map( [ self::class, 'getDatabaseIdForGroupId' ], $ids ), $ids );
319
320 foreach ( $res as $row ) {
321 if ( !isset( $idMap[$row->tgs_group] ) ) {
322 // Stale entry, ignore for now
323 // TODO: Schedule for purge
324 continue;
325 }
326
327 $realId = $idMap[$row->tgs_group];
328 $stats[$realId][$row->tgs_lang] = self::extractNumbers( $row );
329 }
330
331 return $stats;
332 }
333
335 private static function extractNumbers( stdClass $row ): array {
336 return [
337 self::TOTAL => (int)$row->tgs_total,
338 self::TRANSLATED => (int)$row->tgs_translated,
339 self::FUZZY => (int)$row->tgs_fuzzy,
340 self::PROOFREAD => (int)$row->tgs_proofread,
341 ];
342 }
343
350 private static function forLanguageInternal( string $languageCode, array $stats, int $flags ): array {
351 $groups = MessageGroups::singleton()->getGroups();
352
353 $ids = array_keys( $groups );
354 $res = self::selectRowsIdLang( null, [ $languageCode ], $flags );
355 $stats = self::extractResults( $res, $ids, $stats );
356
357 foreach ( $groups as $id => $group ) {
358 if ( isset( $stats[$id][$languageCode] ) ) {
359 continue;
360 }
361 $stats[$id][$languageCode] = self::forItemInternal( $stats, $group, $languageCode, $flags );
362 }
363
364 return $stats;
365 }
366
368 private static function expandAggregates( AggregateMessageGroup $agg ): array {
369 $flattened = [];
370
371 foreach ( $agg->getGroups() as $group ) {
372 if ( $group instanceof AggregateMessageGroup ) {
373 $flattened += self::expandAggregates( $group );
374 } else {
375 $flattened[$group->getId()] = $group;
376 }
377 }
378
379 return $flattened;
380 }
381
388 private static function forGroupInternal( MessageGroup $group, array $stats, int $flags ): array {
389 $id = $group->getId();
390
391 $res = self::selectRowsIdLang( [ $id ], null, $flags );
392 $stats = self::extractResults( $res, [ $id ], $stats );
393
394 // Go over each language filling missing entries
395 $languages = self::getLanguages();
396 foreach ( $languages as $code ) {
397 if ( isset( $stats[$id][$code] ) ) {
398 continue;
399 }
400 $stats[$id][$code] = self::forItemInternal( $stats, $group, $code, $flags );
401 }
402
403 // This is for sorting the values added later in correct order
404 foreach ( array_keys( $stats ) as $key ) {
405 ksort( $stats[$key] );
406 }
407
408 return $stats;
409 }
410
418 private static function selectRowsIdLang( ?array $ids, ?array $codes, int $flags ): iterable {
419 if ( $flags & self::FLAG_NO_CACHE ) {
420 return [];
421 }
422
423 $conditions = [];
424 if ( $ids !== null ) {
425 $dbids = array_map( [ self::class, 'getDatabaseIdForGroupId' ], $ids );
426 $conditions['tgs_group'] = $dbids;
427 }
428
429 if ( $codes !== null ) {
430 $conditions['tgs_lang'] = $codes;
431 }
432
433 $dbr = Utilities::getSafeReadDB();
434 return $dbr->newSelectQueryBuilder()
435 ->select( '*' )
436 ->from( self::TABLE )
437 ->where( $conditions )
438 ->caller( __METHOD__ )
439 ->fetchResultSet();
440 }
441
449 private static function forItemInternal(
450 array &$stats,
451 MessageGroup $group,
452 string $languageCode,
453 int $flags
454 ): array {
455 $id = $group->getId();
456
457 if ( $flags & self::FLAG_CACHE_ONLY ) {
458 $stats[$id][$languageCode] = self::getUnknownStats();
459 return $stats[$id][$languageCode];
460 }
461
462 // It may happen that caches are requested repeatedly for a group before we get a chance
463 // to write the values to the database. Check for queued updates first. This has the
464 // benefit of avoiding duplicate rows for inserts. Ideally this would be checked before we
465 // query the database for missing values. This code is somewhat ugly as it needs to
466 // reverse engineer the values from the row format.
467 $databaseGroupId = self::getDatabaseIdForGroupId( $id );
468 $uniqueKey = "$databaseGroupId|$languageCode";
469 $queuedValue = self::$updates[$uniqueKey] ?? null;
470 if ( $queuedValue && !( $flags & self::FLAG_NO_CACHE ) ) {
471 return [
472 self::TOTAL => $queuedValue['tgs_total'],
473 self::TRANSLATED => $queuedValue['tgs_translated'],
474 self::FUZZY => $queuedValue['tgs_fuzzy'],
475 self::PROOFREAD => $queuedValue['tgs_proofread'],
476 ];
477 }
478
479 if ( $group instanceof AggregateMessageGroup ) {
480 $aggregates = self::calculateAggregateGroup( $stats, $group, $languageCode, $flags );
481 } else {
482 $aggregates = self::calculateGroup( $group, $languageCode );
483 }
484 // Cache for use in subsequent forItemInternal calls
485 $stats[$id][$languageCode] = $aggregates;
486
487 // Don't add nulls to the database, causes annoying warnings
488 if ( $aggregates[self::TOTAL] === null ) {
489 return $aggregates;
490 }
491
492 self::$updates[$uniqueKey] = [
493 'tgs_group' => $databaseGroupId,
494 'tgs_lang' => $languageCode,
495 'tgs_total' => $aggregates[self::TOTAL],
496 'tgs_translated' => $aggregates[self::TRANSLATED],
497 'tgs_fuzzy' => $aggregates[self::FUZZY],
498 'tgs_proofread' => $aggregates[self::PROOFREAD],
499 ];
500
501 // For big and lengthy updates, attempt some interim saves. This might not have
502 // any effect, because writes to the database may be deferred.
503 if ( count( self::$updates ) % 100 === 0 ) {
504 self::queueUpdates( $flags );
505 }
506
507 return $aggregates;
508 }
509
510 private static function calculateAggregateGroup(
511 array &$stats,
513 string $code,
514 int $flags
515 ): array {
516 $aggregates = self::getEmptyStats();
517
518 $expanded = self::expandAggregates( $group );
519 $subGroupIds = array_keys( $expanded );
520
521 // Performance: if we have per-call cache of stats, do not query them again.
522 foreach ( $subGroupIds as $index => $sid ) {
523 if ( isset( $stats[$sid][$code] ) ) {
524 unset( $subGroupIds[ $index ] );
525 }
526 }
527
528 if ( $subGroupIds !== [] ) {
529 $res = self::selectRowsIdLang( $subGroupIds, [ $code ], $flags );
530 $stats = self::extractResults( $res, $subGroupIds, $stats );
531 }
532
533 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
534 foreach ( $expanded as $sid => $subgroup ) {
535 // Discouraged groups may belong to another group, usually if there
536 // is an aggregate group for all translatable pages. In that case
537 // calculate and store the statistics, but don't count them as part of
538 // the aggregate group, so that the numbers in Special:LanguageStats
539 // add up. The statistics for discouraged groups can still be viewed
540 // through Special:MessageGroupStats.
541 if ( !isset( $stats[$sid][$code] ) ) {
542 $stats[$sid][$code] = self::forItemInternal( $stats, $subgroup, $code, $flags );
543 }
544
545 if ( !$messageGroupMetadata->isExcluded( $sid, $code ) ) {
546 $aggregates = self::multiAdd( $aggregates, $stats[$sid][$code] );
547 }
548 }
549
550 return $aggregates;
551 }
552
553 public static function multiAdd( array $a, array $b ): array {
554 if ( $a[0] === null || $b[0] === null ) {
555 return array_fill( 0, count( $a ), null );
556 }
557 foreach ( $a as $i => &$v ) {
558 $v += $b[$i];
559 }
560
561 return $a;
562 }
563
569 private static function calculateGroup( MessageGroup $group, string $languageCode ): array {
570 global $wgTranslateDocumentationLanguageCode;
571 // Calculate if missing and store in the db
572 $collection = $group->initCollection( $languageCode );
573
574 if (
575 $languageCode === $wgTranslateDocumentationLanguageCode
576 && $group instanceof FileBasedMessageGroup
577 ) {
578 $cache = $group->getMessageGroupCache( $group->getSourceLanguage() );
579 if ( $cache->exists() ) {
580 $template = $cache->getExtra()['TEMPLATE'] ?? [];
581 $infile = [];
582 foreach ( $template as $key => $data ) {
583 if ( isset( $data['comments']['.'] ) ) {
584 $infile[$key] = '1';
585 }
586 }
587 $collection->setInFile( $infile );
588 }
589 }
590
591 return self::getStatsForCollection( $collection );
592 }
593
594 private static function queueUpdates( int $flags ): void {
595 $mwInstance = MediaWikiServices::getInstance();
596 if ( self::$updates === [] || $mwInstance->getReadOnlyMode()->isReadOnly() ) {
597 return;
598 }
599
600 $lb = $mwInstance->getDBLoadBalancer();
601 $dbw = $lb->getConnection( DB_PRIMARY ); // avoid connecting yet
602 $callers = wfGetAllCallers( 50 );
603 $functionName = __METHOD__;
604 $callback = static function ( IDatabase $dbw, $method ) use ( $callers, $mwInstance ) {
605 // Maybe another deferred update already processed these
606 if ( self::$updates === [] ) {
607 return;
608 }
609
610 // This path should only be hit during web requests
611 if ( count( self::$updates ) > 100 ) {
612 $groups = array_unique( array_column( self::$updates, 'tgs_group' ) );
613 LoggerFactory::getInstance( 'Translate' )->warning(
614 "Huge translation update of {count} rows for group(s) {groups}",
615 [
616 'count' => count( self::$updates ),
617 'groups' => implode( ', ', $groups ),
618 'callers' => $callers,
619 ]
620 );
621 }
622
623 $primaryKey = [ 'tgs_group', 'tgs_lang' ];
624 $dbw->replace( self::TABLE, [ $primaryKey ], array_values( self::$updates ), $method );
625 self::$updates = [];
626
627 $mwInstance->getMainWANObjectCache()->touchCheckKey( self::LANGUAGE_STATS_KEY );
628 };
629 $updateOp = static function () use ( $dbw, $functionName, $callback ) {
630 $lockName = 'MessageGroupStats:updates';
631 if ( !$dbw->lock( $lockName, $functionName, 1 ) ) {
632 return; // raced out
633 }
634
635 $dbw->commit( $functionName, 'flush' );
636 call_user_func( $callback, $dbw, $functionName );
637 $dbw->commit( $functionName, 'flush' );
638
639 $dbw->unlock( $lockName, $functionName );
640 };
641
642 if ( $flags & self::FLAG_IMMEDIATE_WRITES ) {
643 call_user_func( $updateOp );
644 } else {
645 DeferredUpdates::addCallableUpdate( $updateOp );
646 }
647 }
648
649 public static function getDatabaseIdForGroupId( string $id ): string {
650 // The column is 100 bytes long, but we don't need to use it all
651 if ( strlen( $id ) <= 72 ) {
652 return $id;
653 }
654
655 $hash = hash( 'sha256', $id, /*asHex*/false );
656 return substr( $id, 0, 50 ) . '||' . substr( $hash, 0, 20 );
657 }
658
660 public static function getStatsForCollection( MessageCollection $collection ): array {
661 $collection->filter( 'ignored' );
662 $collection->filterUntranslatedOptional();
663 // Store the count of real messages for later calculation.
664 $total = count( $collection );
665
666 // Count fuzzy first.
667 $collection->filter( 'fuzzy' );
668 $fuzzy = $total - count( $collection );
669
670 // Count the completed translations.
671 $collection->filter( 'hastranslation', false );
672 $translated = count( $collection );
673
674 // Count how many of the completed translations
675 // have been proofread
676 $collection->filter( 'reviewer', false );
677 $proofread = count( $collection );
678
679 return [
680 self::TOTAL => $total,
681 self::TRANSLATED => $translated,
682 self::FUZZY => $fuzzy,
683 self::PROOFREAD => $proofread,
684 ];
685 }
686}
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager( $services->getTitleFactory());}, '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( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), 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: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->getDBLoadBalancer());}, '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->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( 'Translate.MessageGroupSubscription'), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):MessageGroupSubscriptionHookHandler { return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getDBLoadBalancerFactory());}, '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( 'Translate'), $services->getMainObjectStash(), $services->getDBLoadBalancerFactory(), $services->get( 'Translate:MessageGroupSubscription'), 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->getDBLoadBalancer(), $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->getDBLoadBalancer());}, '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->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(), $services->getNamespaceInfo(), $services->getTitleFactory());}, '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->getDBLoadBalancerFactory(), $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:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getDBLoadBalancer(), $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'));}, '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->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getDBLoadBalancerFactory(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getDBLoadBalancer(), $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 { $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
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.
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 $type, bool $condition=true, ?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:58
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(string $groupId, int $flags=0)
Returns stats for all languages in given group.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
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.