Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroups.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
9use DependencyWrapper;
10use InvalidArgumentException;
13use MediaWiki\MediaWikiServices;
14use MessageGroup;
18use MWException;
19use Title;
21use WANObjectCache;
22
35 private static $prioritycache;
37 private $groups;
39 private $groupLoaders;
41 private $cache;
42
49 private const CACHE_VERSION = 4;
50
52 protected function init(): void {
53 if ( is_array( $this->groups ) ) {
54 return; // groups already initialized
55 }
56
57 $value = $this->getCachedGroupDefinitions();
58 $groups = $value['cc'];
59
60 foreach ( $this->getGroupLoaders() as $loader ) {
61 $groups += $loader->getGroups();
62 }
63 $this->initGroupsFromDefinitions( $groups );
64 }
65
67 protected function getCachedGroupDefinitions( $recache = false ): array {
68 global $wgAutoloadClasses;
69
70 $regenerator = function () {
71 global $wgAutoloadClasses;
72
73 $groups = $deps = $autoload = [];
74 // This constructs the list of all groups from multiple different sources.
75 // When possible, a cache dependency is created to automatically recreate
76 // the cache when configuration changes. Currently used by other extensions
77 // such as Banner Messages and test cases to load message groups.
78 MediaWikiServices::getInstance()
79 ->getHookContainer()
80 ->run( 'TranslatePostInitGroups', [ &$groups, &$deps, &$autoload ] );
81 // Register autoloaders for this request, both values modified by reference
82 self::appendAutoloader( $autoload, $wgAutoloadClasses );
83
84 $value = [
85 'ts' => wfTimestamp( TS_MW ),
86 'cc' => $groups,
87 'autoload' => $autoload
88 ];
89 $wrapper = new DependencyWrapper( $value, $deps );
90 $wrapper->initialiseDeps();
91
92 return $wrapper; // save the new value to cache
93 };
94
95 $cache = $this->getCache();
97 $wrapper = $cache->getWithSetCallback(
98 $this->getCacheKey(),
99 $cache::TTL_DAY,
100 $regenerator,
101 [
102 'lockTSE' => 30, // avoid stampedes (mutex)
103 'checkKeys' => [ $this->getCacheKey() ],
104 'touchedCallback' => static function ( $value ) {
105 return ( $value instanceof DependencyWrapper && $value->isExpired() )
106 ? time() // treat value as if it just expired (for "lockTSE")
107 : null;
108 },
109 'minAsOf' => $recache ? INF : $cache::MIN_TIMESTAMP_NONE, // "miss" on recache
110 ]
111 );
112
113 $value = $wrapper->getValue();
114 self::appendAutoloader( $value['autoload'], $wgAutoloadClasses );
115
116 return $value;
117 }
118
124 protected function initGroupsFromDefinitions( array $groups ): void {
125 foreach ( $groups as $id => $mixed ) {
126 if ( !is_object( $mixed ) ) {
127 $groups[$id] = call_user_func( $mixed, $id );
128 }
129 }
130
131 $this->groups = $groups;
132 }
133
135 public function recache(): void {
136 // Purge the value from all datacenters
137 $cache = $this->getCache();
138 $cache->touchCheckKey( $this->getCacheKey() );
139
140 $this->clearProcessCache();
141
142 foreach ( $this->getCacheGroupLoaders() as $cacheLoader ) {
143 $cacheLoader->recache();
144 }
145
146 // Reload the cache value and update the local datacenter
147 $value = $this->getCachedGroupDefinitions( 'recache' );
148 $groups = $value['cc'];
149
150 foreach ( $this->getGroupLoaders() as $loader ) {
151 $groups += $loader->getGroups();
152 }
153
154 $this->initGroupsFromDefinitions( $groups );
155 }
156
162 public static function clearCache(): void {
163 $self = self::singleton();
164
165 $cache = $self->getCache();
166 $cache->delete( $self->getCacheKey(), 1 );
167
168 foreach ( $self->getCacheGroupLoaders() as $cacheLoader ) {
169 $cacheLoader->clearCache();
170 }
171
172 $self->clearProcessCache();
173 }
174
181 public function clearProcessCache(): void {
182 $this->groups = null;
183 $this->groupLoaders = null;
184
185 self::$prioritycache = null;
186 }
187
188 protected function getCache(): WANObjectCache {
189 if ( $this->cache === null ) {
190 return MediaWikiServices::getInstance()->getMainWANObjectCache();
191 } else {
192 return $this->cache;
193 }
194 }
195
197 public function setCache( ?WANObjectCache $cache = null ) {
198 $this->cache = $cache;
199 }
200
202 public function getCacheKey(): string {
203 return $this->getCache()->makeKey( 'translate-groups', 'v' . self::CACHE_VERSION );
204 }
205
212 protected static function appendAutoloader( array &$additions, array &$to ): void {
213 foreach ( $additions as $class => $file ) {
214 if ( isset( $to[$class] ) && $to[$class] !== $file ) {
215 $msg = "Autoload conflict for $class: {$to[$class]} !== $file";
216 trigger_error( $msg, E_USER_WARNING );
217 continue;
218 }
219
220 $to[$class] = $file;
221 }
222 }
223
229 protected function getGroupLoaders(): array {
230 if ( $this->groupLoaders !== null ) {
231 return $this->groupLoaders;
232 }
233
234 $cache = $this->getCache();
235
236 $groupLoaderInstances = $this->groupLoaders = [];
237
238 // Initialize the dependencies
239 $deps = [
240 'database' => Utilities::getSafeReadDB(),
241 'cache' => $cache
242 ];
243
244 MediaWikiServices::getInstance()
245 ->getHookContainer()
246 ->run( 'TranslateInitGroupLoaders', [ &$groupLoaderInstances, $deps ] );
247
248 if ( $groupLoaderInstances === [] ) {
249 return $this->groupLoaders;
250 }
251
252 foreach ( $groupLoaderInstances as $loader ) {
253 if ( !$loader instanceof MessageGroupLoader ) {
254 throw new InvalidArgumentException(
255 "MessageGroupLoader - $loader must implement the " .
256 "MessageGroupLoader interface."
257 );
258 }
259
260 $this->groupLoaders[] = $loader;
261 }
262
263 return $this->groupLoaders;
264 }
265
271 protected function getCacheGroupLoaders(): array {
272 // @phan-suppress-next-line PhanTypeMismatchReturn
273 return array_filter( $this->getGroupLoaders(), static function ( $groupLoader ) {
274 return $groupLoader instanceof CachedMessageGroupLoader;
275 } );
276 }
277
284 public static function getGroup( string $id ): ?MessageGroup {
285 $groups = self::singleton()->getGroups();
286 $id = self::normalizeId( $id );
287
288 if ( isset( $groups[$id] ) ) {
289 return $groups[$id];
290 }
291
292 if ( $id !== '' && $id[0] === '!' ) {
293 $dynamic = self::getDynamicGroups();
294 if ( isset( $dynamic[$id] ) ) {
295 return new $dynamic[$id];
296 }
297 }
298
299 return null;
300 }
301
303 public static function normalizeId( ?string $id ): string {
304 /* Translatable pages use spaces, but MW occasionally likes to
305 * normalize spaces to underscores */
306 $id = (string)$id;
307
308 if ( strpos( $id, 'page-' ) === 0 ) {
309 $id = strtr( $id, '_', ' ' );
310 }
311
312 global $wgTranslateGroupAliases;
313 if ( isset( $wgTranslateGroupAliases[$id] ) ) {
314 $id = $wgTranslateGroupAliases[$id];
315 }
316
317 return $id;
318 }
319
320 public static function exists( string $id ): bool {
321 return (bool)self::getGroup( $id );
322 }
323
325 public static function labelExists( string $name ): bool {
326 $loader = AggregateMessageGroupLoader::getInstance();
327 $labels = array_map( static function ( MessageGroupBase $g ) {
328 return $g->getLabel();
329 }, $loader->loadAggregateGroups() );
330 return in_array( $name, $labels, true );
331 }
332
337 public static function getAllGroups(): array {
338 return self::singleton()->getGroups();
339 }
340
349 public static function getPriority( $group ): string {
350 if ( self::$prioritycache === null ) {
351 self::$prioritycache = [];
352 // Abusing this table originally intended for other purposes
353 $dbr = MediaWikiServices::getInstance()
354 ->getDBLoadBalancer()
355 ->getConnection( DB_REPLICA );
356
357 $res = $dbr->newSelectQueryBuilder()
358 ->select( [ 'tgr_group', 'tgr_state' ] )
359 ->from( 'translate_groupreviews' )
360 ->where( [ 'tgr_lang' => '*priority' ] )
361 ->caller( __METHOD__ )
362 ->fetchResultSet();
363
364 foreach ( $res as $row ) {
365 self::$prioritycache[$row->tgr_group] = $row->tgr_state;
366 }
367 }
368
369 if ( $group instanceof MessageGroup ) {
370 $id = $group->getId();
371 } else {
372 $id = self::normalizeId( $group );
373 }
374
375 return self::$prioritycache[$id] ?? '';
376 }
377
384 public static function setPriority( $group, string $priority = '' ): void {
385 if ( $group instanceof MessageGroup ) {
386 $id = $group->getId();
387 } else {
388 $id = self::normalizeId( $group );
389 }
390
391 // FIXME: This assumes prioritycache has been populated
392 self::$prioritycache[$id] = $priority;
393
394 $dbw = MediaWikiServices::getInstance()
395 ->getDBLoadBalancer()
396 ->getConnection( DB_PRIMARY );
397
398 $table = 'translate_groupreviews';
399 $row = [
400 'tgr_group' => $id,
401 'tgr_lang' => '*priority',
402 'tgr_state' => $priority
403 ];
404
405 if ( $priority === '' ) {
406 unset( $row['tgr_state'] );
407 $dbw->delete( $table, $row, __METHOD__ );
408 } else {
409 $index = [ 'tgr_group', 'tgr_lang' ];
410 $dbw->replace( $table, [ $index ], $row, __METHOD__ );
411 }
412 }
413
414 public static function isDynamic( MessageGroup $group ): bool {
415 $id = $group->getId();
416
417 return ( $id[0] ?? null ) === '!';
418 }
419
424 public static function getSharedGroups( MessageGroup $group ): array {
425 // Take the first message, get a handle for it and check
426 // if that message belongs to other groups. Those are the
427 // parent aggregate groups. Ideally we loop over all keys,
428 // but this should be enough.
429 $keys = array_keys( $group->getDefinitions() );
430 $title = Title::makeTitle( $group->getNamespace(), $keys[0] );
431 $handle = new MessageHandle( $title );
432 $ids = $handle->getGroupIds();
433 foreach ( $ids as $index => $id ) {
434 if ( $id === $group->getId() ) {
435 unset( $ids[$index] );
436 break;
437 }
438 }
439
440 return $ids;
441 }
442
447 public static function getParentGroups( MessageGroup $targetGroup ): array {
448 $ids = self::getSharedGroups( $targetGroup );
449 if ( $ids === [] ) {
450 return [];
451 }
452
453 $targetId = $targetGroup->getId();
454
455 /* Get the group structure. We will be using this to find which
456 * of our candidates are top-level groups. Prefilter it to only
457 * contain aggregate groups. */
458 $structure = self::getGroupStructure();
459 foreach ( $structure as $index => $group ) {
460 if ( $group instanceof MessageGroup ) {
461 unset( $structure[$index] );
462 } else {
463 $structure[$index] = array_shift( $group );
464 }
465 }
466
467 /* Now that we have all related groups, use them to find all paths
468 * from top-level groups to target group with any number of subgroups
469 * in between. */
470 $paths = [];
471
472 /* This function recursively finds paths to the target group */
473 $pathFinder = static function ( &$paths, $group, $targetId, $prefix = '' )
474 use ( &$pathFinder ) {
475 if ( $group instanceof AggregateMessageGroup ) {
476 foreach ( $group->getGroups() as $subgroup ) {
477 $subId = $subgroup->getId();
478 if ( $subId === $targetId ) {
479 $paths[] = $prefix;
480 continue;
481 }
482
483 $pathFinder( $paths, $subgroup, $targetId, "$prefix|$subId" );
484 }
485 }
486 };
487
488 // Iterate over the top-level groups only
489 foreach ( $ids as $id ) {
490 // First, find a top level groups
491 $group = self::getGroup( $id );
492
493 // Quick escape for leaf groups
494 if ( !$group instanceof AggregateMessageGroup ) {
495 continue;
496 }
497
498 foreach ( $structure as $rootGroup ) {
500 if ( $rootGroup->getId() === $group->getId() ) {
501 // Yay we found a top-level group
502 $pathFinder( $paths, $rootGroup, $targetId, $id );
503 break; // No we have one or more paths appended into $paths
504 }
505 }
506 }
507
508 // And finally explode the strings
509 return array_map( static function ( string $pathString ): array {
510 return explode( '|', $pathString );
511 }, $paths );
512 }
513
514 public static function singleton(): self {
515 static $instance;
516 if ( !$instance instanceof self ) {
517 $instance = new self();
518 }
519
520 return $instance;
521 }
522
528 public function getGroups(): array {
529 $this->init();
530
531 return $this->groups;
532 }
533
542 public static function getGroupsById( array $ids, bool $skipMeta = false ): array {
543 $groups = [];
544 foreach ( $ids as $id ) {
545 $group = self::getGroup( $id );
546
547 if ( $group !== null ) {
548 if ( $skipMeta && $group->isMeta() ) {
549 continue;
550 } else {
551 $groups[$id] = $group;
552 }
553 } else {
554 wfDebug( __METHOD__ . ": Invalid message group id: $id\n" );
555 }
556 }
557
558 return $groups;
559 }
560
568 public static function expandWildcards( $ids ): array {
569 $all = [];
570
571 $ids = (array)$ids;
572 foreach ( $ids as $index => $id ) {
573 // Fast path, no wildcards
574 if ( strcspn( $id, '*?' ) === strlen( $id ) ) {
575 $g = self::getGroup( $id );
576 if ( $g ) {
577 $all[] = $g->getId();
578 }
579 unset( $ids[$index] );
580 }
581 }
582
583 if ( $ids === [] ) {
584 return $all;
585 }
586
587 // Slow path for the ones with wildcards
588 $matcher = new StringMatcher( '', $ids );
589 foreach ( self::getAllGroups() as $id => $_ ) {
590 if ( $matcher->matches( $id ) ) {
591 $all[] = $id;
592 }
593 }
594
595 return $all;
596 }
597
599 public static function getDynamicGroups(): array {
600 return [
601 '!recent' => 'RecentMessageGroup',
602 '!additions' => 'RecentAdditionsMessageGroup',
603 '!sandbox' => 'SandboxMessageGroup',
604 ];
605 }
606
616 public static function getGroupsByType( string $type ): array {
617 $groups = self::getAllGroups();
618 foreach ( $groups as $id => $group ) {
619 if ( !$group instanceof $type ) {
620 unset( $groups[$id] );
621 }
622 }
623
624 // @phan-suppress-next-line PhanTypeMismatchReturn
625 return $groups;
626 }
627
638 public static function getGroupStructure(): array {
639 $groups = self::getAllGroups();
640
641 // Determine the top level groups of the tree
642 $tree = $groups;
643 foreach ( $groups as $id => $o ) {
644 if ( !$o->exists() ) {
645 unset( $groups[$id], $tree[$id] );
646 continue;
647 }
648
649 if ( $o instanceof AggregateMessageGroup ) {
650 foreach ( $o->getGroups() as $sid => $so ) {
651 unset( $tree[$sid] );
652 }
653 }
654 }
655
656 usort( $tree, [ self::class, 'groupLabelSort' ] );
657
658 /* Now we have two things left in $tree array:
659 * - solitaries: top-level non-aggregate message groups
660 * - top-level aggregate message groups */
661 foreach ( $tree as $index => $group ) {
662 if ( $group instanceof AggregateMessageGroup ) {
663 $tree[$index] = self::subGroups( $group );
664 }
665 }
666
667 /* Essentially we are done now. Cyclic groups can cause part of the
668 * groups not be included at all, because they have all unset each
669 * other in the first loop. So now we check if there are groups left
670 * over. */
671 $used = [];
672 array_walk_recursive(
673 $tree,
674 static function ( MessageGroup $group ) use ( &$used ) {
675 $used[$group->getId()] = true;
676 }
677 );
678 $unused = array_diff_key( $groups, $used );
679 if ( $unused ) {
680 foreach ( $unused as $index => $group ) {
681 if ( !$group instanceof AggregateMessageGroup ) {
682 unset( $unused[$index] );
683 }
684 }
685
686 // Only list the aggregate groups, other groups cannot cause cycles
687 $participants = implode( ', ', array_keys( $unused ) );
688 throw new MWException( "Found cyclic aggregate message groups: $participants" );
689 }
690
691 return $tree;
692 }
693
695 public static function groupLabelSort( MessageGroup $a, MessageGroup $b ): int {
696 $al = $a->getLabel();
697 $bl = $b->getLabel();
698
699 return strcasecmp( $al, $bl );
700 }
701
712 public static function subGroups(
713 AggregateMessageGroup $parent,
714 array &$childIds = [],
715 string $fname = 'caller'
716 ): array {
717 static $recursionGuard = [];
718
719 $pid = $parent->getId();
720 if ( isset( $recursionGuard[$pid] ) ) {
721 $tid = $pid;
722 $path = [ $tid ];
723 do {
724 $tid = $recursionGuard[$tid];
725 $path[] = $tid;
726 // Until we have gone full cycle
727 } while ( $tid !== $pid );
728 $path = implode( ' > ', $path );
729 throw new MWException( "Found cyclic aggregate message groups: $path" );
730 }
731
732 // We don't care about the ids.
733 $tree = array_values( $parent->getGroups() );
734 usort( $tree, [ self::class, 'groupLabelSort' ] );
735 // Expand aggregate groups (if any left) after sorting to form a tree
736 foreach ( $tree as $index => $group ) {
737 if ( $group instanceof AggregateMessageGroup ) {
738 $sid = $group->getId();
739 $recursionGuard[$pid] = $sid;
740 $tree[$index] = self::subGroups( $group, $childIds, __METHOD__ );
741 unset( $recursionGuard[$pid] );
742
743 $childIds[$sid] = 1;
744 }
745 }
746
747 // Parent group must be first item in the array
748 array_unshift( $tree, $parent );
749
750 if ( $fname !== __METHOD__ ) {
751 // Move the IDs from the keys to the value for final return
752 $childIds = array_values( $childIds );
753 }
754
755 return $tree;
756 }
757
763 public static function haveSingleSourceLanguage( array $groups ): string {
764 $seen = '';
765
766 foreach ( $groups as $group ) {
767 $language = $group->getSourceLanguage();
768 if ( $seen === '' ) {
769 $seen = $language;
770 } elseif ( $language !== $seen ) {
771 return '';
772 }
773 }
774
775 return $seen;
776 }
777
786 public static function isTranslatableMessage( MessageHandle $handle, string $targetLanguage ): bool {
787 static $cache = [];
788
789 if ( !$handle->isValid() ) {
790 return false;
791 }
792
793 $group = $handle->getGroup();
794 $groupId = $group->getId();
795 $cacheKey = "$groupId:$targetLanguage";
796
797 if ( !isset( $cache[$cacheKey] ) ) {
798 $supportedLanguages = Utilities::getLanguageNames( 'en' );
799 $inclusionList = $group->getTranslatableLanguages() ?? $supportedLanguages;
800
801 $included = isset( $inclusionList[$targetLanguage] );
802 $excluded = TranslateMetadata::isExcluded( $groupId, $targetLanguage );
803
804 $cache[$cacheKey] = [
805 'relevant' => $included && !$excluded,
806 'tags' => [],
807 ];
808
809 $groupTags = $group->getTags();
810 foreach ( [ 'ignored', 'optional' ] as $tag ) {
811 if ( isset( $groupTags[$tag] ) ) {
812 foreach ( $groupTags[$tag] as $key ) {
813 // TODO: ucfirst should not be here
814 $cache[$cacheKey]['tags'][ucfirst( $key )] = true;
815 }
816 }
817 }
818 }
819
820 return $cache[$cacheKey]['relevant'] &&
821 !isset( $cache[$cacheKey]['tags'][ucfirst( $handle->getKey() )] );
822 }
823}
824
825class_alias( MessageGroups::class, 'MessageGroups' );
return[ 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'));}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), new RevTagStore(), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(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());}, '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
Loads AggregateMessageGroup, and handles the related cache.
Groups multiple message groups together as one group.
getGroups()
Returns a list of message groups that this group consists of.
Factory class for accessing message groups individually by id or all of them as a list.
static getGroup(string $id)
Fetch a message group by id.
static getPriority( $group)
We want to de-emphasize time sensitive groups like news for 2009.
static getDynamicGroups()
Contents on these groups changes on a whim.
static getGroupsByType(string $type)
Get only groups of specific type (class).
static getSharedGroups(MessageGroup $group)
Returns a list of message groups that share (certain) messages with this group.
static getGroupsById(array $ids, bool $skipMeta=false)
Get message groups for corresponding message group ids.
static isTranslatableMessage(MessageHandle $handle, string $targetLanguage)
Filters out messages that should not be translated under normal conditions.
static getGroupStructure()
Returns a tree of message groups.
setCache(?WANObjectCache $cache=null)
Override cache, for example during tests.
static normalizeId(?string $id)
Fixes the id and resolves aliases.
getGroups()
Get all enabled non-dynamic message groups.
static subGroups(AggregateMessageGroup $parent, array &$childIds=[], string $fname='caller')
Like getGroupStructure but start from one root which must be an AggregateMessageGroup.
getCacheGroupLoaders()
Returns group loaders that implement the CachedMessageGroupLoader.
static appendAutoloader(array &$additions, array &$to)
Safely merges first array to second array, throwing warning on duplicates and removing duplicates fro...
static haveSingleSourceLanguage(array $groups)
Checks whether all the message groups have the same source language.
static labelExists(string $name)
Check if a particular aggregate group label exists.
static expandWildcards( $ids)
If the list of message group ids contains wildcards, this function will match them against the list o...
static groupLabelSort(MessageGroup $a, MessageGroup $b)
Sorts groups by label value.
static setPriority( $group, string $priority='')
Sets the message group priority.
initGroupsFromDefinitions(array $groups)
Expand process cached groups to objects.
static getParentGroups(MessageGroup $targetGroup)
Returns a list of parent message groups.
The versatile default implementation of StringMangler interface.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:30
This class implements some basic functions that wrap around the YAML message group configurations.
getLabel(IContextSource $context=null)
Returns the human readable label (as plain text).
getId()
Returns the unique identifier for this group.
An abstract class to be implemented by group loaders / stores.
Class for pointing to messages, like Title class is for titles.
getGroup()
Get the primary MessageGroup this message belongs to.
isValid()
Checks if the handle corresponds to a known message.
getKey()
Returns the identified or guessed message key.
To be implemented by MessageGroupLoaders that use the MessageGroupWANCache.
Interface for message groups.
getTranslatableLanguages()
Get all the translatable languages for a group, considering the inclusion and exclusion list.
getTags( $type=null)
Returns message tags.
getNamespace()
Returns the namespace where messages are placed.
getId()
Returns the unique identifier for this group.
getLabel(IContextSource $context=null)
Returns the human readable label (as plain text).