Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroups.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
8use InvalidArgumentException;
13use MediaWiki\MediaWikiServices;
14use MediaWiki\Title\Title;
15use MessageGroup;
17use RuntimeException;
18use Wikimedia\ObjectCache\WANObjectCache;
19
32 private $groups;
34 private $groupLoaders;
35 private WANObjectCache $cache;
42 private const CACHE_VERSION = 4;
43
45 protected function init(): void {
46 if ( is_array( $this->groups ) ) {
47 return; // groups already initialized
48 }
49
50 $groups = [];
51 foreach ( $this->getGroupLoaders() as $loader ) {
52 $groups += $loader->getGroups();
53 }
54 $this->initGroupsFromDefinitions( $groups );
55 }
56
62 protected function initGroupsFromDefinitions( array $groups ): void {
63 foreach ( $groups as $id => $mixed ) {
64 if ( !is_object( $mixed ) ) {
65 $groups[$id] = call_user_func( $mixed, $id );
66 }
67 }
68
69 $this->groups = $groups;
70 }
71
73 public function recache(): void {
74 $cache = $this->getCache();
75 $cache->touchCheckKey( $this->getCacheKey() );
76
77 $groups = [];
78 foreach ( $this->getGroupLoaders() as $loader ) {
79 if ( $loader instanceof CachedMessageGroupLoader ) {
80 $groups += $loader->recache();
81 } else {
82 $groups += $loader->getGroups();
83 }
84 }
85 $this->initGroupsFromDefinitions( $groups );
86 }
87
94 public function clearProcessCache(): void {
95 $this->groups = null;
96 $this->groupLoaders = null;
97 }
98
99 protected function getCache(): WANObjectCache {
100 return $this->cache ?? MediaWikiServices::getInstance()->getMainWANObjectCache();
101 }
102
104 public function setCache( WANObjectCache $cache ) {
105 $this->cache = $cache;
106 }
107
109 public function getCacheKey(): string {
110 return $this->getCache()->makeKey( 'translate-groups', 'v' . self::CACHE_VERSION );
111 }
112
118 protected function getGroupLoaders(): array {
119 if ( $this->groupLoaders !== null ) {
120 return $this->groupLoaders;
121 }
122
123 $services = Services::getInstance();
124 $cache = $this->getCache();
125 $dbProvider = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
126
127 $groupLoaderInstances = $this->groupLoaders = [];
128
129 // Initialize the dependencies
130 $deps = [
131 'database' => Utilities::getSafeReadDB(),
132 'cache' => $cache
133 ];
134
135 $services->getHookRunner()->onTranslateInitGroupLoaders( $groupLoaderInstances, $deps );
136
137 foreach ( $groupLoaderInstances as $loader ) {
138 if ( !$loader instanceof MessageGroupLoader ) {
139 throw new InvalidArgumentException(
140 "MessageGroupLoader - $loader must implement the " .
141 'MessageGroupLoader interface.'
142 );
143 }
144
145 $this->groupLoaders[] = $loader;
146 }
147
148 $factories = [
149 $services->getAggregateGroupMessageGroupFactory(),
150 $services->getFileBasedMessageGroupFactory(),
151 $services->getHookDefinedMessageGroupFactory(),
152 $services->getMessageBundleMessageGroupFactory(),
153 $services->getTranslatablePageMessageGroupFactory()
154 ];
155
156 foreach ( $factories as $factory ) {
157 $this->groupLoaders[] = new CachedMessageGroupFactoryLoader(
158 $cache,
159 $dbProvider,
160 $factory
161 );
162 }
163
164 return $this->groupLoaders;
165 }
166
173 public static function getGroup( string $id ): ?MessageGroup {
174 $groups = self::singleton()->getGroups();
175 $id = self::normalizeId( $id );
176
177 if ( isset( $groups[$id] ) ) {
178 return $groups[$id];
179 }
180
181 if ( $id !== '' && $id[0] === '!' ) {
182 $dynamic = self::getDynamicGroups();
183 if ( isset( $dynamic[$id] ) ) {
184 return new $dynamic[$id];
185 }
186 }
187
188 return null;
189 }
190
192 public static function normalizeId( ?string $id ): string {
193 /* Translatable pages use spaces, but MW occasionally likes to
194 * normalize spaces to underscores */
195 $id = (string)$id;
196
197 if ( str_starts_with( $id, 'page-' ) ) {
198 $id = strtr( $id, '_', ' ' );
199 }
200
201 global $wgTranslateGroupAliases;
202 if ( isset( $wgTranslateGroupAliases[$id] ) ) {
203 $id = $wgTranslateGroupAliases[$id];
204 }
205
206 return $id;
207 }
208
209 public static function exists( string $id ): bool {
210 return (bool)self::getGroup( $id );
211 }
212
214 public static function labelExists( string $name ): bool {
215 $groups = self::getAllGroups();
216 foreach ( $groups as $group ) {
217 if ( $group instanceof AggregateMessageGroup ) {
218 if ( $group->getLabel() === $name ) {
219 return true;
220 }
221 }
222
223 }
224
225 return false;
226 }
227
232 public static function getAllGroups(): array {
233 return self::singleton()->getGroups();
234 }
235
244 public static function getPriority( $group ): string {
245 if ( $group instanceof MessageGroup ) {
246 $id = $group->getId();
247 } else {
248 $id = self::normalizeId( $group );
249 }
250
251 return Services::getInstance()->getMessageGroupReviewStore()->getGroupPriority( $id ) ?? '';
252 }
253
260 public static function setPriority( $group, string $priority = '' ): void {
261 if ( $group instanceof MessageGroup ) {
262 $id = $group->getId();
263 } else {
264 $id = self::normalizeId( $group );
265 }
266
267 $priority = $priority === '' ? null : $priority;
268 Services::getInstance()->getMessageGroupReviewStore()->setGroupPriority( $id, $priority );
269 }
270
271 public static function isDynamic( MessageGroup $group ): bool {
272 $id = $group->getId();
273
274 return ( $id[0] ?? null ) === '!';
275 }
276
281 public static function getSharedGroups( MessageGroup $group ): array {
282 // Take the first message, get a handle for it and check
283 // if that message belongs to other groups. Those are the
284 // parent aggregate groups. Ideally we loop over all keys,
285 // but this should be enough.
286 $keys = array_keys( $group->getDefinitions() );
287 $title = Title::makeTitle( $group->getNamespace(), $keys[0] );
288 $handle = new MessageHandle( $title );
289 $ids = $handle->getGroupIds();
290 foreach ( $ids as $index => $id ) {
291 if ( $id === $group->getId() ) {
292 unset( $ids[$index] );
293 break;
294 }
295 }
296
297 return $ids;
298 }
299
304 public static function getParentGroups( MessageGroup $targetGroup ): array {
305 $ids = self::getSharedGroups( $targetGroup );
306 if ( $ids === [] ) {
307 return [];
308 }
309
310 $targetId = $targetGroup->getId();
311
312 /* Get the group structure. We will be using this to find which
313 * of our candidates are top-level groups. Prefilter it to only
314 * contain aggregate groups. */
315 $structure = self::getGroupStructure();
316 foreach ( $structure as $index => $group ) {
317 if ( $group instanceof MessageGroup ) {
318 unset( $structure[$index] );
319 } else {
320 $structure[$index] = array_shift( $group );
321 }
322 }
323
324 /* Now that we have all related groups, use them to find all paths
325 * from top-level groups to target group with any number of subgroups
326 * in between. */
327 $paths = [];
328
329 /* This function recursively finds paths to the target group */
330 $pathFinder = static function ( &$paths, $group, $targetId, $prefix = '' )
331 use ( &$pathFinder ) {
332 if ( $group instanceof AggregateMessageGroup ) {
333 foreach ( $group->getGroups() as $subgroup ) {
334 $subId = $subgroup->getId();
335 if ( $subId === $targetId ) {
336 $paths[] = $prefix;
337 continue;
338 }
339
340 $pathFinder( $paths, $subgroup, $targetId, "$prefix|$subId" );
341 }
342 }
343 };
344
345 // Iterate over the top-level groups only
346 foreach ( $ids as $id ) {
347 // First, find a top level groups
348 $group = self::getGroup( $id );
349
350 // Quick escape for leaf groups
351 if ( !$group instanceof AggregateMessageGroup ) {
352 continue;
353 }
354
355 foreach ( $structure as $rootGroup ) {
357 if ( $rootGroup->getId() === $group->getId() ) {
358 // Yay we found a top-level group
359 $pathFinder( $paths, $rootGroup, $targetId, $id );
360 break; // No we have one or more paths appended into $paths
361 }
362 }
363 }
364
365 // And finally explode the strings
366 return array_map( static function ( string $pathString ): array {
367 return explode( '|', $pathString );
368 }, $paths );
369 }
370
371 public static function singleton(): self {
372 static $instance;
373 if ( !$instance instanceof self ) {
374 $instance = new self();
375 }
376
377 return $instance;
378 }
379
385 public function getGroups(): array {
386 $this->init();
387
388 return $this->groups;
389 }
390
399 public static function getGroupsById( array $ids, bool $skipMeta = false ): array {
400 $groups = [];
401 foreach ( $ids as $id ) {
402 $group = self::getGroup( $id );
403
404 if ( $group !== null ) {
405 if ( $skipMeta && $group->isMeta() ) {
406 continue;
407 } else {
408 $groups[$id] = $group;
409 }
410 } else {
411 wfDebug( __METHOD__ . ": Invalid message group id: $id\n" );
412 }
413 }
414
415 return $groups;
416 }
417
425 public static function expandWildcards( $ids ): array {
426 $all = [];
427
428 $ids = (array)$ids;
429 foreach ( $ids as $index => $id ) {
430 // Fast path, no wildcards
431 if ( strcspn( $id, '*?' ) === strlen( $id ) ) {
432 $g = self::getGroup( $id );
433 if ( $g ) {
434 $all[] = $g->getId();
435 }
436 unset( $ids[$index] );
437 }
438 }
439
440 if ( $ids === [] ) {
441 return $all;
442 }
443
444 // Slow path for the ones with wildcards
445 $matcher = new StringMatcher( '', $ids );
446 foreach ( self::getAllGroups() as $id => $_ ) {
447 if ( $matcher->matches( $id ) ) {
448 $all[] = $id;
449 }
450 }
451
452 return $all;
453 }
454
456 public static function getDynamicGroups(): array {
457 return [
458 '!recent' => 'RecentMessageGroup',
459 '!additions' => 'RecentAdditionsMessageGroup',
460 '!sandbox' => 'SandboxMessageGroup',
461 ];
462 }
463
473 public static function getGroupsByType( string $type ): array {
474 $groups = self::getAllGroups();
475 foreach ( $groups as $id => $group ) {
476 if ( !$group instanceof $type ) {
477 unset( $groups[$id] );
478 }
479 }
480
481 // @phan-suppress-next-line PhanTypeMismatchReturn
482 return $groups;
483 }
484
494 public static function getGroupStructure(): array {
495 $groups = self::getAllGroups();
496
497 // Determine the top level groups of the tree
498 $tree = $groups;
499 foreach ( $groups as $id => $o ) {
500 if ( !$o->exists() ) {
501 unset( $groups[$id], $tree[$id] );
502 continue;
503 }
504
505 if ( $o instanceof AggregateMessageGroup ) {
506 foreach ( $o->getGroups() as $sid => $so ) {
507 unset( $tree[$sid] );
508 }
509 }
510 }
511
512 uasort( $tree, [ self::class, 'groupLabelSort' ] );
513
514 /* Now we have two things left in $tree array:
515 * - solitaries: top-level non-aggregate message groups
516 * - top-level aggregate message groups */
517 foreach ( $tree as $index => $group ) {
518 if ( $group instanceof AggregateMessageGroup ) {
519 $tree[$index] = self::subGroups( $group );
520 }
521 }
522
523 /* Essentially we are done now. Cyclic groups can cause part of the
524 * groups not be included at all, because they have all unset each
525 * other in the first loop. So now we check if there are groups left
526 * over. */
527 $used = [];
528 array_walk_recursive(
529 $tree,
530 static function ( MessageGroup $group ) use ( &$used ) {
531 $used[$group->getId()] = true;
532 }
533 );
534 $unused = array_diff_key( $groups, $used );
535 if ( $unused ) {
536 foreach ( $unused as $index => $group ) {
537 if ( !$group instanceof AggregateMessageGroup ) {
538 unset( $unused[$index] );
539 }
540 }
541
542 // Only list the aggregate groups, other groups cannot cause cycles
543 $participants = implode( ', ', array_keys( $unused ) );
544 throw new RuntimeException( "Found cyclic aggregate message groups: $participants" );
545 }
546
547 return $tree;
548 }
549
551 public static function groupLabelSort( MessageGroup $a, MessageGroup $b ): int {
552 $al = $a->getLabel();
553 $bl = $b->getLabel();
554
555 return strcasecmp( $al, $bl );
556 }
557
567 public static function subGroups(
568 AggregateMessageGroup $parent,
569 array &$childIds = [],
570 string $fname = 'caller'
571 ): array {
572 static $recursionGuard = [];
573
574 $pid = $parent->getId();
575 if ( isset( $recursionGuard[$pid] ) ) {
576 $tid = $pid;
577 $path = [ $tid ];
578 do {
579 $tid = $recursionGuard[$tid];
580 $path[] = $tid;
581 // Until we have gone full cycle
582 } while ( $tid !== $pid );
583 $path = implode( ' > ', $path );
584 throw new RuntimeException( "Found cyclic aggregate message groups: $path" );
585 }
586
587 // We don't care about the ids.
588 $tree = array_values( $parent->getGroups() );
589 usort( $tree, [ self::class, 'groupLabelSort' ] );
590 // Expand aggregate groups (if any left) after sorting to form a tree
591 foreach ( $tree as $index => $group ) {
592 if ( $group instanceof AggregateMessageGroup ) {
593 $sid = $group->getId();
594 $recursionGuard[$pid] = $sid;
595 $tree[$index] = self::subGroups( $group, $childIds, __METHOD__ );
596 unset( $recursionGuard[$pid] );
597
598 $childIds[$sid] = 1;
599 }
600 }
601
602 // Parent group must be first item in the array
603 array_unshift( $tree, $parent );
604
605 if ( $fname !== __METHOD__ ) {
606 // Move the IDs from the keys to the value for final return
607 $childIds = array_values( $childIds );
608 }
609
610 return $tree;
611 }
612
618 public static function haveSingleSourceLanguage( array $groups ): string {
619 $seen = '';
620
621 foreach ( $groups as $group ) {
622 $language = $group->getSourceLanguage();
623 if ( $seen === '' ) {
624 $seen = $language;
625 } elseif ( $language !== $seen ) {
626 return '';
627 }
628 }
629
630 return $seen;
631 }
632
641 public static function isTranslatableMessage( MessageHandle $handle, string $targetLanguage ): bool {
642 static $cache = [];
643
644 if ( !$handle->isValid() ) {
645 return false;
646 }
647
648 $group = $handle->getGroup();
649 $groupId = $group->getId();
650 $cacheKey = "$groupId:$targetLanguage";
651
652 if ( !isset( $cache[$cacheKey] ) ) {
653 $supportedLanguages = Utilities::getLanguageNames( 'en' );
654 $inclusionList = $group->getTranslatableLanguages() ?? $supportedLanguages;
655
656 $included = isset( $inclusionList[$targetLanguage] );
657 $excluded = Services::getInstance()->getMessageGroupMetadata()->isExcluded( $groupId, $targetLanguage );
658
659 $cache[$cacheKey] = [
660 'relevant' => $included && !$excluded,
661 'tags' => [],
662 ];
663
664 $groupTags = $group->getTags();
665 foreach ( [ 'ignored', 'optional' ] as $tag ) {
666 if ( isset( $groupTags[$tag] ) ) {
667 foreach ( $groupTags[$tag] as $key ) {
668 // TODO: ucfirst should not be here
669 $cache[$cacheKey]['tags'][ucfirst( $key )] = true;
670 }
671 }
672 }
673 }
674
675 return $cache[$cacheKey]['relevant'] &&
676 !isset( $cache[$cacheKey]['tags'][ucfirst( $handle->getKey() )] );
677 }
678
680 public function overrideGroupsForTesting( array $groups ): void {
681 $this->groups = $groups;
682 }
683}
684
685class_alias( MessageGroups::class, 'MessageGroups' );
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(), 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 { 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());}, '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->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.
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.
static normalizeId(?string $id)
Fixes the id and resolves aliases.
setCache(WANObjectCache $cache)
Override cache, for example during tests.
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.
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.
recache()
Clear message group caches and populate message groups from uncached data.
static getParentGroups(MessageGroup $targetGroup)
Returns a list of parent message groups.
Class for pointing to messages, like Title class is for titles.
getKey()
Returns the identified or guessed message key.
getGroup()
Get the primary MessageGroup this message belongs to.
isValid()
Checks if the handle corresponds to a known message.
The versatile default implementation of StringMangler interface.
Minimal service container.
Definition Services.php:59
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
getId()
Returns the unique identifier for this group.
Interface for MessageGroupFactories that use caching.
Interface for message group loaders.
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).