Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageCollection.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageLoading;
5
6use AppendIterator;
7use ArrayAccess;
8use Countable;
9use EmptyIterator;
10use Iterator;
14use MediaWiki\MediaWikiServices;
15use MediaWiki\Revision\RevisionRecord;
16use MediaWiki\Revision\SlotRecord;
17use MWException;
18use stdClass;
19use TextContent;
20use ThinMessage;
21use TitleValue;
22use TMessage;
23use Traversable;
24use Wikimedia\Rdbms\IDatabase;
25
38class MessageCollection implements ArrayAccess, Iterator, Countable {
45 private const MAX_ITEMS_PER_QUERY = 2000;
46
48 public $code;
50 private $definitions = null;
52 private $infile = [];
53 // Keys and messages.
54
56 protected $keys = [];
58 protected $messages = [];
60 private $reverseMap;
61 // Database resources
62
64 private $dbInfo;
66 private $dbData;
68 private $dbReviewData;
74 protected $tags = [];
79 private $properties = [];
81 private $authors = [];
82
87 public function __construct( string $code ) {
88 $this->code = $code;
89 }
90
96 public static function newFromDefinitions( MessageDefinitions $definitions, string $code ): self {
97 $collection = new self( $code );
98 $collection->definitions = $definitions;
99 $collection->resetForNewLanguage( $code );
100
101 return $collection;
102 }
103
104 public function getLanguage(): string {
105 return $this->code;
106 }
107
108 // Data setters
109
115 public function setInFile( array $messages ): void {
116 $this->infile = $messages;
117 }
118
124 public function setTags( string $type, array $keys ): void {
125 $this->tags[$type] = $keys;
126 }
127
132 public function keys(): array {
133 return $this->keys;
134 }
135
140 private function getTitles(): array {
141 return array_values( $this->keys );
142 }
143
148 public function getMessageKeys(): array {
149 return array_keys( $this->keys );
150 }
151
157 public function getTags( string $type ): array {
158 return $this->tags[$type] ?? [];
159 }
160
167 public function getAuthors(): array {
168 $this->loadTranslations();
169
170 $authors = array_flip( $this->authors );
171
172 foreach ( $this->messages as $m ) {
173 // Check if there are authors
175 $author = $m->getProperty( 'last-translator-text' );
176
177 if ( $author === null ) {
178 continue;
179 }
180
181 if ( !isset( $authors[$author] ) ) {
182 $authors[$author] = 1;
183 } else {
184 $authors[$author]++;
185 }
186 }
187
188 # arsort( $authors, SORT_NUMERIC );
189 ksort( $authors );
190 $fuzzyBot = FuzzyBot::getName();
191 $filteredAuthors = [];
192 foreach ( $authors as $author => $edits ) {
193 if ( $author !== $fuzzyBot ) {
194 $filteredAuthors[] = $author;
195 }
196 }
197
198 return $filteredAuthors;
199 }
200
207 public function addCollectionAuthors( array $authors, string $mode = 'append' ): void {
208 switch ( $mode ) {
209 case 'append':
210 $authors = array_merge( $this->authors, $authors );
211 break;
212 case 'set':
213 break;
214 default:
215 throw new MWException( "Invalid mode $mode" );
216 }
217
218 $this->authors = array_unique( $authors );
219 }
220
221 // Data modifiers
222
227 public function loadTranslations(): void {
228 // Performance optimization: Instead of building conditions based on key in every
229 // method, build them once and pass it on to each of them.
230 $dbr = Utilities::getSafeReadDB();
231 $titleConds = $this->getTitleConds( $dbr );
232
233 $this->loadData( $this->keys, $titleConds );
234 $this->loadInfo( $this->keys, $titleConds );
235 $this->loadReviewInfo( $this->keys, $titleConds );
236 $this->initMessages();
237 }
238
243 public function resetForNewLanguage( string $code ): void {
244 $this->code = $code;
245 $this->keys = $this->fixKeys();
246 $this->dbInfo = [];
247 $this->dbData = [];
248 $this->dbReviewData = [];
249 $this->messages = null;
250 $this->infile = [];
251 $this->authors = [];
252
253 unset( $this->tags['fuzzy'] );
254 $this->reverseMap = null;
255 }
256
264 public function slice( $offset, $limit ) {
265 $indexes = array_keys( $this->keys );
266
267 if ( $offset === '' ) {
268 $offset = 0;
269 }
270
271 // Handle string offsets
272 if ( !ctype_digit( (string)$offset ) ) {
273 $pos = array_search( $offset, array_keys( $this->keys ), true );
274 // Now offset is always an integer, suitable for array_slice
275 $offset = $pos !== false ? $pos : count( $this->keys );
276 }
277
278 // False means that cannot go back or forward
279 $backwardsOffset = $forwardsOffset = false;
280 // Backwards paging uses numerical indexes, see below
281
282 // Can only skip this if no offset has been provided or the
283 // offset is zero. (offset - limit ) > 1 does not work, because
284 // users can end in offest=2, limit=5 and can't see the first
285 // two messages. That's also why it is capped into zero with
286 // max(). And finally make the offsets to be strings even if
287 // they are numbers in this case.
288 if ( $offset > 0 ) {
289 $backwardsOffset = (string)( max( 0, $offset - $limit ) );
290 }
291
292 // Forwards paging uses keys. If user opens view Untranslated,
293 // translates some messages and then clicks next, the first
294 // message visible in the page is the first message not shown
295 // in the previous page (unless someone else translated it at
296 // the same time). If we used integer offsets, we would skip
297 // same number of messages that were translated, because they
298 // are no longer in the list. For backwards paging this is not
299 // such a big issue, so it still uses integer offsets, because
300 // we would need to also implement "direction" to have it work
301 // correctly.
302 if ( isset( $indexes[$offset + $limit] ) ) {
303 $forwardsOffset = $indexes[$offset + $limit];
304 }
305
306 $this->keys = array_slice( $this->keys, $offset, $limit, true );
307
308 return [ $backwardsOffset, $forwardsOffset, $offset ];
309 }
310
334 public function filter( string $type, bool $condition = true, ?int $value = null ): void {
335 if ( !in_array( $type, self::getAvailableFilters(), true ) ) {
336 throw new MWException( "Unknown filter $type" );
337 }
338 $this->applyFilter( $type, $condition, $value );
339 }
340
341 private static function getAvailableFilters(): array {
342 return [
343 'fuzzy',
344 'optional',
345 'ignored',
346 'hastranslation',
347 'changed',
348 'translated',
349 'reviewer',
350 'last-translator',
351 ];
352 }
353
362 private function applyFilter( string $filter, bool $condition, ?int $value ): void {
363 $keys = $this->keys;
364 if ( $filter === 'fuzzy' ) {
365 $keys = $this->filterFuzzy( $keys, $condition );
366 } elseif ( $filter === 'hastranslation' ) {
367 $keys = $this->filterHastranslation( $keys, $condition );
368 } elseif ( $filter === 'translated' ) {
369 $fuzzy = $this->filterFuzzy( $keys, false );
370 $hastranslation = $this->filterHastranslation( $keys, false );
371 // Fuzzy messages are not counted as translated messages
372 $translated = $this->filterOnCondition( $hastranslation, $fuzzy );
373 $keys = $this->filterOnCondition( $keys, $translated, $condition );
374 } elseif ( $filter === 'changed' ) {
375 $keys = $this->filterChanged( $keys, $condition );
376 } elseif ( $filter === 'reviewer' ) {
377 $keys = $this->filterReviewer( $keys, $condition, $value );
378 } elseif ( $filter === 'last-translator' ) {
379 $keys = $this->filterLastTranslator( $keys, $condition, $value );
380 } else {
381 // Filter based on tags.
382 if ( !isset( $this->tags[$filter] ) ) {
383 if ( $filter !== 'optional' && $filter !== 'ignored' ) {
384 throw new MWException( "No tagged messages for custom filter $filter" );
385 }
386 $keys = $this->filterOnCondition( $keys, [], $condition );
387 } else {
388 $taggedKeys = array_flip( $this->tags[$filter] );
389 $keys = $this->filterOnCondition( $keys, $taggedKeys, $condition );
390 }
391 }
392
393 $this->keys = $keys;
394 }
395
397 public function filterUntranslatedOptional(): void {
398 $optionalKeys = array_flip( $this->tags['optional'] ?? [] );
399 // Convert plain message keys to array<string,TitleValue>
400 $optional = $this->filterOnCondition( $this->keys, $optionalKeys, false );
401 // Then get reduce that list to those which have no translation. Ensure we don't
402 // accidentally populate the info cache with too few keys.
403 $this->loadInfo( $this->keys );
404 $untranslatedOptional = $this->filterHastranslation( $optional, true );
405 // Now remove that list from the full list
406 $this->keys = $this->filterOnCondition( $this->keys, $untranslatedOptional );
407 }
408
424 private function filterOnCondition( array $keys, array $condKeys, bool $condition = true ): array {
425 if ( $condition ) {
426 // Delete $condKeys from $keys
427 foreach ( array_keys( $condKeys ) as $key ) {
428 unset( $keys[$key] );
429 }
430 } else {
431 // Keep the keys which are in $condKeys
432 foreach ( array_keys( $keys ) as $key ) {
433 if ( !isset( $condKeys[$key] ) ) {
434 unset( $keys[$key] );
435 }
436 }
437 }
438
439 return $keys;
440 }
441
449 private function filterFuzzy( array $keys, bool $condition ): array {
450 $this->loadInfo( $keys );
451
452 $origKeys = [];
453 if ( !$condition ) {
454 $origKeys = $keys;
455 }
456
457 foreach ( $this->dbInfo as $row ) {
458 if ( $row->rt_type !== null ) {
459 unset( $keys[$this->rowToKey( $row )] );
460 }
461 }
462
463 if ( !$condition ) {
464 $keys = array_diff( $origKeys, $keys );
465 }
466
467 return $keys;
468 }
469
477 private function filterHastranslation( array $keys, bool $condition ): array {
478 $this->loadInfo( $keys );
479
480 $origKeys = [];
481 if ( !$condition ) {
482 $origKeys = $keys;
483 }
484
485 foreach ( $this->dbInfo as $row ) {
486 unset( $keys[$this->rowToKey( $row )] );
487 }
488
489 // Check also if there is something in the file that is not yet in the database
490 foreach ( array_keys( $this->infile ) as $inf ) {
491 unset( $keys[$inf] );
492 }
493
494 // Remove the messages which do not have a translation from the list
495 if ( !$condition ) {
496 $keys = array_diff( $origKeys, $keys );
497 }
498
499 return $keys;
500 }
501
510 private function filterChanged( array $keys, bool $condition ): array {
511 $this->loadData( $keys );
512
513 $origKeys = [];
514 if ( !$condition ) {
515 $origKeys = $keys;
516 }
517
518 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
519 $infileRows = [];
520 foreach ( $this->dbData as $row ) {
521 $mkey = $this->rowToKey( $row );
522 if ( isset( $this->infile[$mkey] ) ) {
523 $infileRows[] = $row;
524 }
525 }
526
527 $revisions = $revStore->newRevisionsFromBatch( $infileRows, [
528 'slots' => [ SlotRecord::MAIN ],
529 'content' => true
530 ] )->getValue();
531 foreach ( $infileRows as $row ) {
533 $rev = $revisions[$row->rev_id];
534 if ( $rev ) {
536 $content = $rev->getContent( SlotRecord::MAIN );
537 if ( $content ) {
538 $mkey = $this->rowToKey( $row );
539 if ( $this->infile[$mkey] === $content->getText() ) {
540 // Remove unchanged messages from the list
541 unset( $keys[$mkey] );
542 }
543 }
544 }
545 }
546
547 // Remove the messages which have changed from the original list
548 if ( !$condition ) {
549 $keys = $this->filterOnCondition( $origKeys, $keys );
550 }
551
552 return $keys;
553 }
554
563 private function filterReviewer( array $keys, bool $condition, ?int $userId ): array {
564 $this->loadReviewInfo( $keys );
565 $origKeys = $keys;
566
567 /* This removes messages from the list which have certain
568 * reviewer (among others) */
569 foreach ( $this->dbReviewData as $row ) {
570 if ( $userId === null || (int)$row->trr_user === $userId ) {
571 unset( $keys[$this->rowToKey( $row )] );
572 }
573 }
574
575 if ( !$condition ) {
576 $keys = array_diff( $origKeys, $keys );
577 }
578
579 return $keys;
580 }
581
588 private function filterLastTranslator( array $keys, bool $condition, ?int $userId ): array {
589 $this->loadData( $keys );
590 $origKeys = $keys;
591
592 $userId = $userId ?? 0;
593 foreach ( $this->dbData as $row ) {
594 if ( (int)$row->rev_user === $userId ) {
595 unset( $keys[$this->rowToKey( $row )] );
596 }
597 }
598
599 if ( !$condition ) {
600 $keys = array_diff( $origKeys, $keys );
601 }
602
603 return $keys;
604 }
605
610 private function fixKeys(): array {
611 $newkeys = [];
612
613 $pages = $this->definitions->getPages();
614 foreach ( $pages as $key => $baseTitle ) {
615 $newkeys[$key] = new TitleValue(
616 $baseTitle->getNamespace(),
617 $baseTitle->getDBkey() . '/' . $this->code
618 );
619 }
620
621 return $newkeys;
622 }
623
629 private function loadInfo( array $keys, ?array $titleConds = null ): void {
630 if ( $this->dbInfo !== [] ) {
631 return;
632 }
633
634 if ( !count( $keys ) ) {
635 $this->dbInfo = new EmptyIterator();
636 return;
637 }
638
639 $dbr = Utilities::getSafeReadDB();
640 $tables = [ 'page', 'revtag' ];
641 $fields = [ 'page_namespace', 'page_title', 'rt_type' ];
642 $joins = [ 'revtag' =>
643 [
644 'LEFT JOIN',
645 [ 'page_id=rt_page', 'page_latest=rt_revision', 'rt_type' => RevTagStore::FUZZY_TAG ]
646 ]
647 ];
648
649 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
650 $iterator = new AppendIterator();
651 foreach ( $titleConds as $conds ) {
652 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
653 }
654
655 $this->dbInfo = $iterator;
656
657 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
658 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
659 // contain all the entries that are present in our $iterator and will throw notices.
660 $this->getReverseMap();
661 }
662
668 private function loadReviewInfo( array $keys, ?array $titleConds = null ): void {
669 if ( $this->dbReviewData !== [] ) {
670 return;
671 }
672
673 if ( !count( $keys ) ) {
674 $this->dbReviewData = new EmptyIterator();
675 return;
676 }
677
678 $dbr = Utilities::getSafeReadDB();
679 $tables = [ 'page', 'translate_reviews' ];
680 $fields = [ 'page_namespace', 'page_title', 'trr_user' ];
681 $joins = [ 'translate_reviews' =>
682 [
683 'JOIN',
684 [ 'page_id=trr_page', 'page_latest=trr_revision' ]
685 ]
686 ];
687
688 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
689 $iterator = new AppendIterator();
690 foreach ( $titleConds as $conds ) {
691 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
692 }
693
694 $this->dbReviewData = $iterator;
695
696 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
697 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
698 // contain all the entries that are present in our $iterator and will throw notices.
699 $this->getReverseMap();
700 }
701
707 private function loadData( array $keys, ?array $titleConds = null ): void {
708 if ( $this->dbData !== [] ) {
709 return;
710 }
711
712 if ( !count( $keys ) ) {
713 $this->dbData = new EmptyIterator();
714 return;
715 }
716
717 $dbr = Utilities::getSafeReadDB();
718 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
719 $revQuery = $revisionStore->getQueryInfo( [ 'page' ] );
720 $tables = $revQuery['tables'];
721 $fields = $revQuery['fields'];
722 $joins = $revQuery['joins'];
723
724 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
725 $iterator = new AppendIterator();
726 foreach ( $titleConds as $conds ) {
727 $conds = [ 'page_latest = rev_id', $conds ];
728 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
729 }
730
731 $this->dbData = $iterator;
732
733 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
734 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
735 // contain all the entries that are present in our $iterator and will throw notices.
736 $this->getReverseMap();
737 }
738
743 private function getTitleConds( IDatabase $db ): array {
744 $titles = $this->getTitles();
745 $chunks = array_chunk( $titles, self::MAX_ITEMS_PER_QUERY );
746 $results = [];
747
748 foreach ( $chunks as $titles ) {
749 // Array of array( namespace, pagename )
750 $byNamespace = [];
751 foreach ( $titles as $title ) {
752 $namespace = $title->getNamespace();
753 $pagename = $title->getDBkey();
754 $byNamespace[$namespace][] = $pagename;
755 }
756
757 $conds = [];
758 foreach ( $byNamespace as $namespaces => $pagenames ) {
759 $cond = [
760 'page_namespace' => $namespaces,
761 'page_title' => $pagenames,
762 ];
763
764 $conds[] = $db->makeList( $cond, LIST_AND );
765 }
766
767 $results[] = $db->makeList( $conds, LIST_OR );
768 }
769
770 return $results;
771 }
772
778 private function rowToKey( stdClass $row ): ?string {
779 $map = $this->getReverseMap();
780 if ( isset( $map[$row->page_namespace][$row->page_title] ) ) {
781 return $map[$row->page_namespace][$row->page_title];
782 } else {
783 wfWarn( "Got unknown title from the database: {$row->page_namespace}:{$row->page_title}" );
784
785 return null;
786 }
787 }
788
790 private function getReverseMap(): array {
791 if ( isset( $this->reverseMap ) ) {
792 return $this->reverseMap;
793 }
794
795 $map = [];
797 foreach ( $this->keys as $mkey => $title ) {
798 $map[$title->getNamespace()][$title->getDBkey()] = $mkey;
799 }
800
801 $this->reverseMap = $map;
802 return $this->reverseMap;
803 }
804
809 public function initMessages(): void {
810 if ( $this->messages !== null ) {
811 return;
812 }
813
814 $messages = [];
815 $definitions = $this->definitions->getDefinitions();
816 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
817 $queryFlags = Utilities::shouldReadFromPrimary() ? $revStore::READ_LATEST : 0;
818 foreach ( array_keys( $this->keys ) as $mkey ) {
819 $messages[$mkey] = new ThinMessage( $mkey, $definitions[$mkey] );
820 }
821
822 if ( $this->dbData !== null ) {
823 $slotRows = $revStore->getContentBlobsForBatch(
824 $this->dbData, [ SlotRecord::MAIN ], $queryFlags
825 )->getValue();
826
827 foreach ( $this->dbData as $row ) {
828 $mkey = $this->rowToKey( $row );
829 if ( !isset( $messages[$mkey] ) ) {
830 continue;
831 }
832 $messages[$mkey]->setRow( $row );
833 $messages[$mkey]->setProperty( 'revision', $row->page_latest );
834
835 if ( isset( $slotRows[$row->rev_id][SlotRecord::MAIN] ) ) {
836 $slot = $slotRows[$row->rev_id][SlotRecord::MAIN];
837 $messages[$mkey]->setTranslation( $slot->blob_data );
838 }
839 }
840 }
841
842 if ( $this->dbInfo !== null ) {
843 $fuzzy = [];
844 foreach ( $this->dbInfo as $row ) {
845 if ( $row->rt_type !== null ) {
846 $fuzzy[] = $this->rowToKey( $row );
847 }
848 }
849
850 $this->setTags( 'fuzzy', $fuzzy );
851 }
852
853 // Copy tags if any.
854 foreach ( $this->tags as $type => $keys ) {
855 foreach ( $keys as $mkey ) {
856 if ( isset( $messages[$mkey] ) ) {
857 $messages[$mkey]->addTag( $type );
858 }
859 }
860 }
861
862 // Copy properties if any.
863 foreach ( $this->properties as $type => $keys ) {
864 foreach ( $keys as $mkey => $value ) {
865 if ( isset( $messages[$mkey] ) ) {
866 $messages[$mkey]->setProperty( $type, $value );
867 }
868 }
869 }
870
871 // Copy infile if any.
872 foreach ( $this->infile as $mkey => $value ) {
873 if ( isset( $messages[$mkey] ) ) {
874 $messages[$mkey]->setInfile( $value );
875 }
876 }
877
878 foreach ( $this->dbReviewData as $row ) {
879 $mkey = $this->rowToKey( $row );
880 if ( !isset( $messages[$mkey] ) ) {
881 continue;
882 }
883 $messages[$mkey]->appendProperty( 'reviewers', $row->trr_user );
884 }
885
886 // Set the status property
887 foreach ( $messages as $obj ) {
888 if ( $obj->hasTag( 'fuzzy' ) ) {
889 $obj->setProperty( 'status', 'fuzzy' );
890 } elseif ( is_array( $obj->getProperty( 'reviewers' ) ) ) {
891 $obj->setProperty( 'status', 'proofread' );
892 } elseif ( $obj->translation() !== null ) {
893 $obj->setProperty( 'status', 'translated' );
894 } else {
895 $obj->setProperty( 'status', 'untranslated' );
896 }
897 }
898
899 $this->messages = $messages;
900 }
901
906 public function offsetExists( $offset ): bool {
907 return isset( $this->keys[$offset] );
908 }
909
911 public function offsetGet( $offset ): ?TMessage {
912 return $this->messages[$offset] ?? null;
913 }
914
919 public function offsetSet( $offset, $value ): void {
920 $this->messages[$offset] = $value;
921 }
922
924 public function offsetUnset( $offset ): void {
925 unset( $this->keys[$offset] );
926 }
927
935 public function __get( string $name ): void {
936 throw new MWException( __METHOD__ . ": Trying to access unknown property $name" );
937 }
938
945 public function __set( string $name, $value ): void {
946 throw new MWException( __METHOD__ . ": Trying to modify unknown property $name" );
947 }
948
954 public function rewind(): void {
955 reset( $this->keys );
956 }
957
958 #[\ReturnTypeWillChange]
959 public function current() {
960 if ( !count( $this->keys ) ) {
961 return false;
962 }
963
964 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
965 return $this->messages[key( $this->keys )];
966 }
967
968 public function key(): ?string {
969 return key( $this->keys );
970 }
971
972 public function next(): void {
973 next( $this->keys );
974 }
975
976 public function valid(): bool {
977 return isset( $this->messages[key( $this->keys )] );
978 }
979
980 public function count(): int {
981 return count( $this->keys() );
982 }
983
985}
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
Class to manage revision tags for translatable bundles.
This file contains the class for core message collections implementation.
resetForNewLanguage(string $code)
Some statistics scripts for example loop the same collection over every language.
getAuthors()
Lists all translators that have contributed to the latest revisions of each translation.
filter(string $type, bool $condition=true, ?int $value=null)
Filters messages based on some condition.
addCollectionAuthors(array $authors, string $mode='append')
Add external authors (usually from the file).
static newFromDefinitions(MessageDefinitions $definitions, string $code)
Construct a new message collection from definitions.
setInFile(array $messages)
Set translation from file, as opposed to translation which only exists in the wiki because they are n...
__get(string $name)
Fail fast if trying to access unknown properties.
initMessages()
Constructs all TMessages from the data accumulated so far.
getMessageKeys()
Returns list of message keys that are used in this collection after filtering.
__set(string $name, $value)
Fail fast if trying to access unknown properties.
Wrapper for message definitions, just to beauty the code.
FuzzyBot - the misunderstood workhorse.
Definition FuzzyBot.php:15
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:30
Interface for message objects used by MessageCollection.
Definition Message.php:14
Message object which is based on database result row.
Definition Message.php:132