35 private const TABLE =
'translate_groupstats';
37 private const LANGUAGE_STATS_KEY =
'translate-all-language-stats';
52 private static array $updates = [];
54 private static ?array $languages =
null;
68 private static function getUnknownStats(): array {
69 return [ null, null, null, null ];
72 private static function isValidLanguage(
string $languageCode ): bool {
73 $languages = self::getLanguages();
74 return in_array( $languageCode, $languages );
81 private static function isValidMessageGroup( ?
MessageGroup $group ): bool {
82 return $group && !MessageGroups::isDynamic( $group );
92 public static function forItem(
string $groupId,
string $languageCode,
int $flags = 0 ): array {
94 if ( !self::isValidMessageGroup( $group ) || !self::isValidLanguage( $languageCode ) ) {
95 return self::getUnknownStats();
98 $res = self::selectRowsIdLang( [ $groupId ], [ $languageCode ], $flags );
99 $stats = self::extractResults( $res, [ $groupId ] );
101 if ( !isset( $stats[$groupId][$languageCode] ) ) {
102 $stats[$groupId][$languageCode] = self::forItemInternal( $stats, $group, $languageCode, $flags );
105 self::queueUpdates( $flags );
107 return $stats[$groupId][$languageCode];
116 public static function forLanguage(
string $languageCode,
int $flags = 0 ): array {
117 if ( !self::isValidLanguage( $languageCode ) ) {
119 $groups = MessageGroups::singleton()->getGroups();
120 $ids = array_keys( $groups );
121 foreach ( $ids as $id ) {
122 $stats[$id] = self::getUnknownStats();
128 $stats = self::forLanguageInternal( $languageCode, [], $flags );
130 foreach ( $stats as $group => $languages ) {
131 $flattened[$group] = $languages[$languageCode];
134 self::queueUpdates( $flags );
145 public static function forGroup(
string $groupId,
int $flags = 0 ): array {
147 if ( !self::isValidMessageGroup( $group ) ) {
148 $languages = self::getLanguages();
150 foreach ( $languages as $code ) {
151 $stats[$code] = self::getUnknownStats();
157 $stats = self::forGroupInternal( $group, [], $flags );
159 self::queueUpdates( $flags );
161 return $stats[$groupId];
171 $code = $handle->getCode();
172 if ( !self::isValidLanguage( $code ) ) {
175 $groups = self::getSortedGroupsForClearing( $handle->
getGroupIds() );
176 self::internalClearGroups( $code, $groups, 0 );
185 public static function clearGroup( $id,
int $flags = 0 ): void {
186 $languages = self::getLanguages();
187 $groups = self::getSortedGroupsForClearing( (array)$id );
190 foreach ( $languages as $code ) {
191 self::internalClearGroups( $code, $groups, $flags );
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 );
210 return self::getAllLanguageStats();
213 'checkKeys' => [ self::LANGUAGE_STATS_KEY ],
214 'pcTTL' => $cache::TTL_PROC_SHORT,
219 private static function getAllLanguageStats(): array {
221 $res = $dbr->newSelectQueryBuilder()
222 ->table( self::TABLE )
225 'tgs_translated' =>
'SUM(tgs_translated)',
226 'tgs_fuzzy' =>
'SUM(tgs_fuzzy)',
227 'tgs_total' =>
'SUM(tgs_total)',
228 'tgs_proofread' =>
'SUM(tgs_proofread)'
230 ->groupBy(
'tgs_lang' )
231 ->caller( __METHOD__ )
234 $allLanguages = self::getLanguages();
235 $languagesCodes = array_flip( $allLanguages );
238 foreach ( $res as $row ) {
239 $allStats[ $row->tgs_lang ] = self::extractNumbers( $row );
240 unset( $languagesCodes[ $row->tgs_lang ] );
244 foreach ( array_keys( $languagesCodes ) as $code ) {
245 $allStats[ $code ] = self::getEmptyStats();
257 private static function internalClearGroups(
string $code, array $groups,
int $flags ): void {
259 foreach ( $groups as $group ) {
261 self::forItemInternal( $stats, $group, $code, $flags );
263 self::queueUpdates( 0 );
276 private static function getSortedGroupsForClearing( array $ids ): array {
277 $groups = array_map( [ MessageGroups::class,
'getGroup' ], $ids );
279 $groups = array_filter( $groups );
283 foreach ( $groups as $group ) {
285 $aggregates[$group->getId()] = $group;
287 $sorted[$group->getId()] = $group;
291 return array_merge( $sorted, $aggregates );
299 if ( self::$languages === null ) {
300 $languages = array_keys( Utilities::getLanguageNames(
'en' ) );
302 self::$languages = $languages;
305 return self::$languages;
316 private static function extractResults( iterable $res, array $ids, array $stats = [] ): array {
318 $idMap = array_combine( array_map( [ self::class,
'getDatabaseIdForGroupId' ], $ids ), $ids );
320 foreach ( $res as $row ) {
321 if ( !isset( $idMap[$row->tgs_group] ) ) {
327 $realId = $idMap[$row->tgs_group];
328 $stats[$realId][$row->tgs_lang] = self::extractNumbers( $row );
335 private static function extractNumbers( stdClass $row ): array {
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,
350 private static function forLanguageInternal(
string $languageCode, array $stats,
int $flags ): array {
351 $groups = MessageGroups::singleton()->getGroups();
353 $ids = array_keys( $groups );
354 $res = self::selectRowsIdLang(
null, [ $languageCode ], $flags );
355 $stats = self::extractResults( $res, $ids, $stats );
357 foreach ( $groups as $id => $group ) {
358 if ( isset( $stats[$id][$languageCode] ) ) {
361 $stats[$id][$languageCode] = self::forItemInternal( $stats, $group, $languageCode, $flags );
371 foreach ( $agg->
getGroups() as $group ) {
373 $flattened += self::expandAggregates( $group );
375 $flattened[$group->getId()] = $group;
388 private static function forGroupInternal(
MessageGroup $group, array $stats,
int $flags ): array {
389 $id = $group->getId();
391 $res = self::selectRowsIdLang( [ $id ],
null, $flags );
392 $stats = self::extractResults( $res, [ $id ], $stats );
395 $languages = self::getLanguages();
396 foreach ( $languages as $code ) {
397 if ( isset( $stats[$id][$code] ) ) {
400 $stats[$id][$code] = self::forItemInternal( $stats, $group, $code, $flags );
404 foreach ( array_keys( $stats ) as $key ) {
405 ksort( $stats[$key] );
418 private static function selectRowsIdLang( ?array $ids, ?array $codes,
int $flags ): iterable {
419 if ( $flags & self::FLAG_NO_CACHE ) {
424 if ( $ids !==
null ) {
425 $dbids = array_map( [ self::class,
'getDatabaseIdForGroupId' ], $ids );
426 $conditions[
'tgs_group'] = $dbids;
429 if ( $codes !==
null ) {
430 $conditions[
'tgs_lang'] = $codes;
433 $dbr = Utilities::getSafeReadDB();
434 return $dbr->newSelectQueryBuilder()
436 ->from( self::TABLE )
437 ->where( $conditions )
438 ->caller( __METHOD__ )
449 private static function forItemInternal(
452 string $languageCode,
455 $id = $group->getId();
457 if ( $flags & self::FLAG_CACHE_ONLY ) {
458 $stats[$id][$languageCode] = self::getUnknownStats();
459 return $stats[$id][$languageCode];
467 $databaseGroupId = self::getDatabaseIdForGroupId( $id );
468 $uniqueKey =
"$databaseGroupId|$languageCode";
469 $queuedValue = self::$updates[$uniqueKey] ??
null;
470 if ( $queuedValue && !( $flags & self::FLAG_NO_CACHE ) ) {
472 self::TOTAL => $queuedValue[
'tgs_total'],
473 self::TRANSLATED => $queuedValue[
'tgs_translated'],
474 self::FUZZY => $queuedValue[
'tgs_fuzzy'],
475 self::PROOFREAD => $queuedValue[
'tgs_proofread'],
480 $aggregates = self::calculateAggregateGroup( $stats, $group, $languageCode, $flags );
482 $aggregates = self::calculateGroup( $group, $languageCode );
485 $stats[$id][$languageCode] = $aggregates;
488 if ( $aggregates[self::TOTAL] ===
null ) {
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],
503 if ( count( self::$updates ) % 100 === 0 ) {
504 self::queueUpdates( $flags );
510 private static function calculateAggregateGroup(
516 $aggregates = self::getEmptyStats();
518 $expanded = self::expandAggregates( $group );
519 $subGroupIds = array_keys( $expanded );
522 foreach ( $subGroupIds as $index => $sid ) {
523 if ( isset( $stats[$sid][$code] ) ) {
524 unset( $subGroupIds[ $index ] );
528 if ( $subGroupIds !== [] ) {
529 $res = self::selectRowsIdLang( $subGroupIds, [ $code ], $flags );
530 $stats = self::extractResults( $res, $subGroupIds, $stats );
533 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
534 foreach ( $expanded as $sid => $subgroup ) {
541 if ( !isset( $stats[$sid][$code] ) ) {
542 $stats[$sid][$code] = self::forItemInternal( $stats, $subgroup, $code, $flags );
545 if ( !$messageGroupMetadata->isExcluded( $sid, $code ) ) {
546 $aggregates = self::multiAdd( $aggregates, $stats[$sid][$code] );
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 );
557 foreach ( $a as $i => &$v ) {
569 private static function calculateGroup(
MessageGroup $group,
string $languageCode ): array {
570 global $wgTranslateDocumentationLanguageCode;
575 $languageCode === $wgTranslateDocumentationLanguageCode
579 if ( $cache->exists() ) {
580 $template = $cache->getExtra()[
'TEMPLATE'] ?? [];
582 foreach ( $template as $key => $data ) {
583 if ( isset( $data[
'comments'][
'.'] ) ) {
587 $collection->setInFile( $infile );
591 return self::getStatsForCollection( $collection );
594 private static function queueUpdates(
int $flags ): void {
595 $mwInstance = MediaWikiServices::getInstance();
596 if ( self::$updates === [] || $mwInstance->getReadOnlyMode()->isReadOnly() ) {
600 $lb = $mwInstance->getDBLoadBalancer();
601 $dbw = $lb->getConnection( DB_PRIMARY );
602 $callers = wfGetAllCallers( 50 );
603 $functionName = __METHOD__;
604 $callback =
static function ( IDatabase $dbw, $method ) use ( $callers, $mwInstance ) {
606 if ( self::$updates === [] ) {
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}",
616 'count' => count( self::$updates ),
617 'groups' => implode(
', ', $groups ),
618 'callers' => $callers,
623 $primaryKey = [
'tgs_group',
'tgs_lang' ];
624 $dbw->replace( self::TABLE, [ $primaryKey ], array_values( self::$updates ), $method );
627 $mwInstance->getMainWANObjectCache()->touchCheckKey( self::LANGUAGE_STATS_KEY );
629 $updateOp =
static function () use ( $dbw, $functionName, $callback ) {
630 $lockName =
'MessageGroupStats:updates';
631 if ( !$dbw->lock( $lockName, $functionName, 1 ) ) {
635 $dbw->commit( $functionName,
'flush' );
636 call_user_func( $callback, $dbw, $functionName );
637 $dbw->commit( $functionName,
'flush' );
639 $dbw->unlock( $lockName, $functionName );
642 if ( $flags & self::FLAG_IMMEDIATE_WRITES ) {
643 call_user_func( $updateOp );
645 DeferredUpdates::addCallableUpdate( $updateOp );
649 public static function getDatabaseIdForGroupId(
string $id ): string {
651 if ( strlen( $id ) <= 72 ) {
655 $hash = hash(
'sha256', $id,
false );
656 return substr( $id, 0, 50 ) .
'||' . substr( $hash, 0, 20 );
661 $collection->filter(
'ignored' );
662 $collection->filterUntranslatedOptional();
664 $total = count( $collection );
667 $collection->
filter(
'fuzzy' );
668 $fuzzy = $total - count( $collection );
671 $collection->
filter(
'hastranslation',
false );
672 $translated = count( $collection );
676 $collection->
filter(
'reviewer',
false );
677 $proofread = count( $collection );
680 self::TOTAL => $total,
681 self::TRANSLATED => $translated,
682 self::FUZZY => $fuzzy,
683 self::PROOFREAD => $proofread,
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