Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageCollection.php
Go to the documentation of this file.
1<?php
13use MediaWiki\MediaWikiServices;
14use MediaWiki\Revision\RevisionRecord;
15use MediaWiki\Revision\SlotRecord;
16
25class MessageCollection implements ArrayAccess, Iterator, Countable {
32 private const MAX_ITEMS_PER_QUERY = 2000;
33
35 public $code;
37 private $definitions = null;
39 private $infile = [];
40 // Keys and messages.
41
43 protected $keys = [];
45 protected $messages = [];
47 private $reverseMap;
48 // Database resources
49
51 private $dbInfo;
53 private $dbData;
55 private $dbReviewData;
61 protected $tags = [];
66 private $properties = [];
68 private $authors = [];
69
74 public function __construct( $code ) {
75 $this->code = $code;
76 }
77
84 public static function newFromDefinitions( MessageDefinitions $definitions, $code ) {
85 $collection = new self( $code );
86 $collection->definitions = $definitions;
87 $collection->resetForNewLanguage( $code );
88
89 return $collection;
90 }
91
93 public function getLanguage() {
94 return $this->code;
95 }
96
97 // Data setters
98
104 public function setInFile( array $messages ) {
105 $this->infile = $messages;
106 }
107
113 public function setTags( $type, array $keys ) {
114 $this->tags[$type] = $keys;
115 }
116
121 public function keys() {
122 return $this->keys;
123 }
124
130 public function getTitles() {
131 return array_values( $this->keys );
132 }
133
139 public function getMessageKeys() {
140 return array_keys( $this->keys );
141 }
142
148 public function getTags( $type ) {
149 return $this->tags[$type] ?? [];
150 }
151
158 public function getAuthors() {
159 $this->loadTranslations();
160
161 $authors = array_flip( $this->authors );
162
163 foreach ( $this->messages as $m ) {
164 // Check if there are authors
166 $author = $m->getProperty( 'last-translator-text' );
167
168 if ( $author === null ) {
169 continue;
170 }
171
172 if ( !isset( $authors[$author] ) ) {
173 $authors[$author] = 1;
174 } else {
175 $authors[$author]++;
176 }
177 }
178
179 # arsort( $authors, SORT_NUMERIC );
180 ksort( $authors );
181 $fuzzyBot = FuzzyBot::getName();
182 $filteredAuthors = [];
183 foreach ( $authors as $author => $edits ) {
184 if ( $author !== $fuzzyBot ) {
185 $filteredAuthors[] = $author;
186 }
187 }
188
189 return $filteredAuthors;
190 }
191
198 public function addCollectionAuthors( $authors, $mode = 'append' ) {
199 switch ( $mode ) {
200 case 'append':
201 $authors = array_merge( $this->authors, $authors );
202 break;
203 case 'set':
204 break;
205 default:
206 throw new MWException( "Invalid mode $mode" );
207 }
208
209 $this->authors = array_unique( $authors );
210 }
211
212 // Data modifiers
213
218 public function loadTranslations() {
219 // Performance optimization: Instead of building conditions based on key in every
220 // method, build them once and pass it on to each of them.
221 $dbr = TranslateUtils::getSafeReadDB();
222 $titleConds = $this->getTitleConds( $dbr );
223
224 $this->loadData( $this->keys, $titleConds );
225 $this->loadInfo( $this->keys, $titleConds );
226 $this->loadReviewInfo( $this->keys, $titleConds );
227 $this->initMessages();
228 }
229
235 public function resetForNewLanguage( $code ) {
236 $this->code = $code;
237 $this->keys = $this->fixKeys();
238 $this->dbInfo = [];
239 $this->dbData = [];
240 $this->dbReviewData = [];
241 $this->messages = null;
242 $this->infile = [];
243 $this->authors = [];
244
245 unset( $this->tags['fuzzy'] );
246 $this->reverseMap = null;
247 }
248
256 public function slice( $offset, $limit ) {
257 $indexes = array_keys( $this->keys );
258
259 if ( $offset === '' ) {
260 $offset = 0;
261 }
262
263 // Handle string offsets
264 if ( !ctype_digit( (string)$offset ) ) {
265 $pos = array_search( $offset, array_keys( $this->keys ), true );
266 // Now offset is always an integer, suitable for array_slice
267 $offset = $pos !== false ? $pos : count( $this->keys );
268 }
269
270 // False means that cannot go back or forward
271 $backwardsOffset = $forwardsOffset = false;
272 // Backwards paging uses numerical indexes, see below
273
274 // Can only skip this if no offset has been provided or the
275 // offset is zero. (offset - limit ) > 1 does not work, because
276 // users can end in offest=2, limit=5 and can't see the first
277 // two messages. That's also why it is capped into zero with
278 // max(). And finally make the offsets to be strings even if
279 // they are numbers in this case.
280 if ( $offset > 0 ) {
281 $backwardsOffset = (string)( max( 0, $offset - $limit ) );
282 }
283
284 // Forwards paging uses keys. If user opens view Untranslated,
285 // translates some messages and then clicks next, the first
286 // message visible in the page is the first message not shown
287 // in the previous page (unless someone else translated it at
288 // the same time). If we used integer offsets, we would skip
289 // same number of messages that were translated, because they
290 // are no longer in the list. For backwards paging this is not
291 // such a big issue, so it still uses integer offsets, because
292 // we would need to also implement "direction" to have it work
293 // correctly.
294 if ( isset( $indexes[$offset + $limit] ) ) {
295 $forwardsOffset = $indexes[$offset + $limit];
296 }
297
298 $this->keys = array_slice( $this->keys, $offset, $limit, true );
299
300 return [ $backwardsOffset, $forwardsOffset, $offset ];
301 }
302
326 public function filter( $type, $condition = true, $value = null ) {
327 if ( !in_array( $type, self::getAvailableFilters(), true ) ) {
328 throw new MWException( "Unknown filter $type" );
329 }
330 $this->applyFilter( $type, $condition, $value );
331 }
332
334 public static function getAvailableFilters() {
335 return [
336 'fuzzy',
337 'optional',
338 'ignored',
339 'hastranslation',
340 'changed',
341 'translated',
342 'reviewer',
343 'last-translator',
344 ];
345 }
346
355 protected function applyFilter( $filter, $condition, $value ) {
356 $keys = $this->keys;
357 if ( $filter === 'fuzzy' ) {
358 $keys = $this->filterFuzzy( $keys, $condition );
359 } elseif ( $filter === 'hastranslation' ) {
360 $keys = $this->filterHastranslation( $keys, $condition );
361 } elseif ( $filter === 'translated' ) {
362 $fuzzy = $this->filterFuzzy( $keys, false );
363 $hastranslation = $this->filterHastranslation( $keys, false );
364 // Fuzzy messages are not counted as translated messages
365 $translated = $this->filterOnCondition( $hastranslation, $fuzzy );
366 $keys = $this->filterOnCondition( $keys, $translated, $condition );
367 } elseif ( $filter === 'changed' ) {
368 $keys = $this->filterChanged( $keys, $condition );
369 } elseif ( $filter === 'reviewer' ) {
370 $keys = $this->filterReviewer( $keys, $condition, $value );
371 } elseif ( $filter === 'last-translator' ) {
372 $keys = $this->filterLastTranslator( $keys, $condition, $value );
373 } else {
374 // Filter based on tags.
375 if ( !isset( $this->tags[$filter] ) ) {
376 if ( $filter !== 'optional' && $filter !== 'ignored' ) {
377 throw new MWException( "No tagged messages for custom filter $filter" );
378 }
379 $keys = $this->filterOnCondition( $keys, [], $condition );
380 } else {
381 $taggedKeys = array_flip( $this->tags[$filter] );
382 $keys = $this->filterOnCondition( $keys, $taggedKeys, $condition );
383 }
384 }
385
386 $this->keys = $keys;
387 }
388
390 public function filterUntranslatedOptional(): void {
391 $optionalKeys = array_flip( $this->tags['optional'] ?? [] );
392 // Convert plain message keys to array<string,TitleValue>
393 $optional = $this->filterOnCondition( $this->keys, $optionalKeys, false );
394 // Then get reduce that list to those which have no translation. Ensure we don't
395 // accidentally populate the info cache with too few keys.
396 $this->loadInfo( $this->keys );
397 $untranslatedOptional = $this->filterHastranslation( $optional, true );
398 // Now remove that list from the full list
399 $this->keys = $this->filterOnCondition( $this->keys, $untranslatedOptional );
400 }
401
417 protected function filterOnCondition( array $keys, array $condKeys, $condition = true ) {
418 if ( $condition === true ) {
419 // Delete $condKeys from $keys
420 foreach ( array_keys( $condKeys ) as $key ) {
421 unset( $keys[$key] );
422 }
423 } else {
424 // Keep the keys which are in $condKeys
425 foreach ( array_keys( $keys ) as $key ) {
426 if ( !isset( $condKeys[$key] ) ) {
427 unset( $keys[$key] );
428 }
429 }
430 }
431
432 return $keys;
433 }
434
442 protected function filterFuzzy( array $keys, $condition ) {
443 $this->loadInfo( $keys );
444
445 $origKeys = [];
446 if ( $condition === false ) {
447 $origKeys = $keys;
448 }
449
450 foreach ( $this->dbInfo as $row ) {
451 if ( $row->rt_type !== null ) {
452 unset( $keys[$this->rowToKey( $row )] );
453 }
454 }
455
456 if ( $condition === false ) {
457 $keys = array_diff( $origKeys, $keys );
458 }
459
460 return $keys;
461 }
462
470 protected function filterHastranslation( array $keys, $condition ) {
471 $this->loadInfo( $keys );
472
473 $origKeys = [];
474 if ( $condition === false ) {
475 $origKeys = $keys;
476 }
477
478 foreach ( $this->dbInfo as $row ) {
479 unset( $keys[$this->rowToKey( $row )] );
480 }
481
482 // Check also if there is something in the file that is not yet in the database
483 foreach ( array_keys( $this->infile ) as $inf ) {
484 unset( $keys[$inf] );
485 }
486
487 // Remove the messages which do not have a translation from the list
488 if ( $condition === false ) {
489 $keys = array_diff( $origKeys, $keys );
490 }
491
492 return $keys;
493 }
494
503 protected function filterChanged( array $keys, $condition ) {
504 $this->loadData( $keys );
505
506 $origKeys = [];
507 if ( $condition === false ) {
508 $origKeys = $keys;
509 }
510
511 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
512 $infileRows = [];
513 foreach ( $this->dbData as $row ) {
514 $mkey = $this->rowToKey( $row );
515 if ( isset( $this->infile[$mkey] ) ) {
516 $infileRows[] = $row;
517 }
518 }
519
520 $revisions = $revStore->newRevisionsFromBatch( $infileRows, [
521 'slots' => [ SlotRecord::MAIN ],
522 'content' => true
523 ] )->getValue();
524 foreach ( $infileRows as $row ) {
526 $rev = $revisions[$row->rev_id];
527 if ( $rev ) {
529 $content = $rev->getContent( SlotRecord::MAIN );
530 if ( $content ) {
531 $mkey = $this->rowToKey( $row );
532 if ( $this->infile[$mkey] === $content->getText() ) {
533 // Remove unchanged messages from the list
534 unset( $keys[$mkey] );
535 }
536 }
537 }
538 }
539
540 // Remove the messages which have changed from the original list
541 if ( $condition === false ) {
542 $keys = $this->filterOnCondition( $origKeys, $keys );
543 }
544
545 return $keys;
546 }
547
556 protected function filterReviewer( array $keys, $condition, $user ) {
557 $this->loadReviewInfo( $keys );
558 $origKeys = $keys;
559
560 /* This removes messages from the list which have certain
561 * reviewer (among others) */
562 $userId = (int)$user;
563 foreach ( $this->dbReviewData as $row ) {
564 if ( $user === null || (int)$row->trr_user === $userId ) {
565 unset( $keys[$this->rowToKey( $row )] );
566 }
567 }
568
569 if ( $condition === false ) {
570 $keys = array_diff( $origKeys, $keys );
571 }
572
573 return $keys;
574 }
575
583 protected function filterLastTranslator( array $keys, $condition, $user ) {
584 $this->loadData( $keys );
585 $origKeys = $keys;
586
587 $user = (int)$user;
588 foreach ( $this->dbData as $row ) {
589 if ( (int)$row->rev_user === $user ) {
590 unset( $keys[$this->rowToKey( $row )] );
591 }
592 }
593
594 if ( $condition === false ) {
595 $keys = array_diff( $origKeys, $keys );
596 }
597
598 return $keys;
599 }
600
605 protected function fixKeys() {
606 $newkeys = [];
607
608 $pages = $this->definitions->getPages();
609 foreach ( $pages as $key => $baseTitle ) {
610 $newkeys[$key] = new TitleValue(
611 $baseTitle->getNamespace(),
612 $baseTitle->getDBkey() . '/' . $this->code
613 );
614 }
615
616 return $newkeys;
617 }
618
624 protected function loadInfo( array $keys, ?array $titleConds = null ) {
625 if ( $this->dbInfo !== [] ) {
626 return;
627 }
628
629 if ( !count( $keys ) ) {
630 $this->dbInfo = new EmptyIterator();
631 return;
632 }
633
634 $dbr = TranslateUtils::getSafeReadDB();
635 $tables = [ 'page', 'revtag' ];
636 $fields = [ 'page_namespace', 'page_title', 'rt_type' ];
637 $joins = [ 'revtag' =>
638 [
639 'LEFT JOIN',
640 [ 'page_id=rt_page', 'page_latest=rt_revision', 'rt_type' => RevTagStore::FUZZY_TAG ]
641 ]
642 ];
643
644 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
645 $iterator = new AppendIterator();
646 foreach ( $titleConds as $conds ) {
647 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
648 }
649
650 $this->dbInfo = $iterator;
651
652 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
653 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
654 // contain all the entries that are present in our $iterator and will throw notices.
655 $this->getReverseMap();
656 }
657
663 protected function loadReviewInfo( array $keys, ?array $titleConds = null ) {
664 if ( $this->dbReviewData !== [] ) {
665 return;
666 }
667
668 if ( !count( $keys ) ) {
669 $this->dbReviewData = new EmptyIterator();
670 return;
671 }
672
673 $dbr = TranslateUtils::getSafeReadDB();
674 $tables = [ 'page', 'translate_reviews' ];
675 $fields = [ 'page_namespace', 'page_title', 'trr_user' ];
676 $joins = [ 'translate_reviews' =>
677 [
678 'JOIN',
679 [ 'page_id=trr_page', 'page_latest=trr_revision' ]
680 ]
681 ];
682
683 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
684 $iterator = new AppendIterator();
685 foreach ( $titleConds as $conds ) {
686 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
687 }
688
689 $this->dbReviewData = $iterator;
690
691 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
692 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
693 // contain all the entries that are present in our $iterator and will throw notices.
694 $this->getReverseMap();
695 }
696
702 protected function loadData( array $keys, ?array $titleConds = null ) {
703 if ( $this->dbData !== [] ) {
704 return;
705 }
706
707 if ( !count( $keys ) ) {
708 $this->dbData = new EmptyIterator();
709 return;
710 }
711
712 $dbr = TranslateUtils::getSafeReadDB();
713 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
714 $revQuery = $revisionStore->getQueryInfo( [ 'page' ] );
715 $tables = $revQuery['tables'];
716 $fields = $revQuery['fields'];
717 $joins = $revQuery['joins'];
718
719 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
720 $iterator = new AppendIterator();
721 foreach ( $titleConds as $conds ) {
722 $conds = [ 'page_latest = rev_id', $conds ];
723 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
724 }
725
726 $this->dbData = $iterator;
727
728 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
729 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
730 // contain all the entries that are present in our $iterator and will throw notices.
731 $this->getReverseMap();
732 }
733
740 protected function getTitleConds( $db ) {
741 $titles = $this->getTitles();
742 $chunks = array_chunk( $titles, self::MAX_ITEMS_PER_QUERY );
743 $results = [];
744
745 foreach ( $chunks as $titles ) {
746 // Array of array( namespace, pagename )
747 $byNamespace = [];
748 foreach ( $titles as $title ) {
749 $namespace = $title->getNamespace();
750 $pagename = $title->getDBkey();
751 $byNamespace[$namespace][] = $pagename;
752 }
753
754 $conds = [];
755 foreach ( $byNamespace as $namespaces => $pagenames ) {
756 $cond = [
757 'page_namespace' => $namespaces,
758 'page_title' => $pagenames,
759 ];
760
761 $conds[] = $db->makeList( $cond, LIST_AND );
762 }
763
764 $results[] = $db->makeList( $conds, LIST_OR );
765 }
766
767 return $results;
768 }
769
778 protected function rowToKey( $row ) {
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
794 public function getReverseMap() {
795 if ( isset( $this->reverseMap ) ) {
796 return $this->reverseMap;
797 }
798
799 $map = [];
801 foreach ( $this->keys as $mkey => $title ) {
802 $map[$title->getNamespace()][$title->getDBkey()] = $mkey;
803 }
804
805 $this->reverseMap = $map;
806 return $this->reverseMap;
807 }
808
813 public function initMessages() {
814 if ( $this->messages !== null ) {
815 return;
816 }
817
818 $messages = [];
819 $definitions = $this->definitions->getDefinitions();
820 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
821 $queryFlags = TranslateUtils::shouldReadFromPrimary() ? $revStore::READ_LATEST : 0;
822 foreach ( array_keys( $this->keys ) as $mkey ) {
823 $messages[$mkey] = new ThinMessage( $mkey, $definitions[$mkey] );
824 }
825
826 if ( $this->dbData !== null ) {
827 $slotRows = $revStore->getContentBlobsForBatch(
828 $this->dbData, [ SlotRecord::MAIN ], $queryFlags
829 )->getValue();
830
831 foreach ( $this->dbData as $row ) {
832 $mkey = $this->rowToKey( $row );
833 if ( !isset( $messages[$mkey] ) ) {
834 continue;
835 }
836 $messages[$mkey]->setRow( $row );
837 $messages[$mkey]->setProperty( 'revision', $row->page_latest );
838
839 if ( isset( $slotRows[$row->rev_id][SlotRecord::MAIN] ) ) {
840 $slot = $slotRows[$row->rev_id][SlotRecord::MAIN];
841 $messages[$mkey]->setTranslation( $slot->blob_data );
842 }
843 }
844 }
845
846 if ( $this->dbInfo !== null ) {
847 $fuzzy = [];
848 foreach ( $this->dbInfo as $row ) {
849 if ( $row->rt_type !== null ) {
850 $fuzzy[] = $this->rowToKey( $row );
851 }
852 }
853
854 $this->setTags( 'fuzzy', $fuzzy );
855 }
856
857 // Copy tags if any.
858 foreach ( $this->tags as $type => $keys ) {
859 foreach ( $keys as $mkey ) {
860 if ( isset( $messages[$mkey] ) ) {
861 $messages[$mkey]->addTag( $type );
862 }
863 }
864 }
865
866 // Copy properties if any.
867 foreach ( $this->properties as $type => $keys ) {
868 foreach ( $keys as $mkey => $value ) {
869 if ( isset( $messages[$mkey] ) ) {
870 $messages[$mkey]->setProperty( $type, $value );
871 }
872 }
873 }
874
875 // Copy infile if any.
876 foreach ( $this->infile as $mkey => $value ) {
877 if ( isset( $messages[$mkey] ) ) {
878 $messages[$mkey]->setInfile( $value );
879 }
880 }
881
882 foreach ( $this->dbReviewData as $row ) {
883 $mkey = $this->rowToKey( $row );
884 if ( !isset( $messages[$mkey] ) ) {
885 continue;
886 }
887 $messages[$mkey]->appendProperty( 'reviewers', $row->trr_user );
888 }
889
890 // Set the status property
891 foreach ( $messages as $obj ) {
892 if ( $obj->hasTag( 'fuzzy' ) ) {
893 $obj->setProperty( 'status', 'fuzzy' );
894 } elseif ( is_array( $obj->getProperty( 'reviewers' ) ) ) {
895 $obj->setProperty( 'status', 'proofread' );
896 } elseif ( $obj->translation() !== null ) {
897 $obj->setProperty( 'status', 'translated' );
898 } else {
899 $obj->setProperty( 'status', 'untranslated' );
900 }
901 }
902
903 $this->messages = $messages;
904 }
905
911 public function offsetExists( $offset ): bool {
912 return isset( $this->keys[$offset] );
913 }
914
919 public function offsetGet( $offset ): ?TMessage {
920 return $this->messages[$offset] ?? null;
921 }
922
927 public function offsetSet( $offset, $value ): void {
928 $this->messages[$offset] = $value;
929 }
930
932 public function offsetUnset( $offset ): void {
933 unset( $this->keys[$offset] );
934 }
935
944 public function __get( $name ) {
945 throw new MWException( __METHOD__ . ": Trying to access unknown property $name" );
946 }
947
955 public function __set( $name, $value ) {
956 throw new MWException( __METHOD__ . ": Trying to modify unknown property $name" );
957 }
958
964 public function rewind(): void {
965 reset( $this->keys );
966 }
967
968 #[\ReturnTypeWillChange]
969 public function current() {
970 if ( !count( $this->keys ) ) {
971 return false;
972 }
973
974 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
975 return $this->messages[key( $this->keys )];
976 }
977
978 public function key(): ?string {
979 return key( $this->keys );
980 }
981
982 public function next(): void {
983 next( $this->keys );
984 }
985
986 public function valid(): bool {
987 return isset( $this->messages[key( $this->keys )] );
988 }
989
990 public function count(): int {
991 return count( $this->keys() );
992 }
993
995}
996
1004 private $namespace;
1006 private $messages;
1008 private $pages;
1009
1014 public function __construct( array $messages, $namespace = false ) {
1015 $this->namespace = $namespace;
1016 $this->messages = $messages;
1017 }
1018
1020 public function getDefinitions() {
1021 return $this->messages;
1022 }
1023
1025 public function getPages() {
1026 $namespace = $this->namespace;
1027 if ( $this->pages !== null ) {
1028 return $this->pages;
1029 }
1030
1031 $pages = [];
1032 foreach ( array_keys( $this->messages ) as $key ) {
1033 if ( $namespace === false ) {
1034 // pages are in format ex. "8:jan"
1035 [ $tns, $tkey ] = explode( ':', $key, 2 );
1036 $title = Title::makeTitleSafe( $tns, $tkey );
1037 } else {
1038 $title = Title::makeTitleSafe( $namespace, $key );
1039 }
1040
1041 if ( !$title ) {
1042 wfWarn( "Invalid title ($namespace:)$key" );
1043 continue;
1044 }
1045
1046 $pages[$key] = $title;
1047 }
1048
1049 $this->pages = $pages;
1050
1051 return $this->pages;
1052 }
1053}
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'), MessageIndex::singleton());}, '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: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: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: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());}, '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.
FuzzyBot - the misunderstood workhorse.
Definition FuzzyBot.php:15
Core message collection class.
setInFile(array $messages)
Set translation from file, as opposed to translation which only exists in the wiki because they are n...
__construct( $code)
Constructors.
filterReviewer(array $keys, $condition, $user)
Filters list of keys according to whether the user has accepted them.
filterHastranslation(array $keys, $condition)
Filters list of keys according to whether they have a translation.
applyFilter( $filter, $condition, $value)
Really apply a filter.
filterOnCondition(array $keys, array $condKeys, $condition=true)
Filters list of keys with other list of keys according to the condition.
filterFuzzy(array $keys, $condition)
Filters list of keys according to whether the translation is fuzzy.
getTitleConds( $db)
Of the current set of keys, construct database query conditions.
filterLastTranslator(array $keys, $condition, $user)
getAuthors()
Lists all translators that have contributed to the latest revisions of each translation.
fixKeys()
Takes list of keys and converts them into database format.
getTitles()
Returns list of TitleValues of messages that are used in this collection after filtering.
loadReviewInfo(array $keys, ?array $titleConds=null)
Loads reviewers for given messages.
getReverseMap()
Creates a two-dimensional map of namespace and pagenames.
static newFromDefinitions(MessageDefinitions $definitions, $code)
Construct a new message collection from definitions.
offsetExists( $offset)
ArrayAccess methods.
initMessages()
Constructs all TMessages from the data accumulated so far.
filter( $type, $condition=true, $value=null)
Filters messages based on some condition.
resetForNewLanguage( $code)
Some statistics scripts for example loop the same collection over every language.
__get( $name)
Fail fast if trying to access unknown properties.
loadTranslations()
Loads all message data.
setTags( $type, array $keys)
Set message tags.
getTags( $type)
Returns stored message tags.
offsetSet( $offset, $value)
loadInfo(array $keys, ?array $titleConds=null)
Loads existence and fuzzy state for given list of keys.
rewind()
Iterator method.
getMessageKeys()
Returns list of message keys that are used in this collection after filtering.
filterChanged(array $keys, $condition)
Filters list of keys according to whether the current translation differs from the commited translati...
slice( $offset, $limit)
For paging messages.
addCollectionAuthors( $authors, $mode='append')
Add external authors (usually from the file).
loadData(array $keys, ?array $titleConds=null)
Loads translation for given list of keys.
__set( $name, $value)
Fail fast if trying to access unknown properties.
keys()
Returns list of available message keys.
rowToKey( $row)
Given two-dimensional map of namespace and pagenames, this uses database fields page_namespace and pa...
Wrapper for message definitions, just to beauty the code.
__construct(array $messages, $namespace=false)
Interface for message objects used by MessageCollection.
Definition Message.php:14
Message object which is based on database result row.
Definition Message.php:132