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 InvalidArgumentException;
11use Iterator;
12use LogicException;
16use MediaWiki\MediaWikiServices;
17use MediaWiki\Revision\RevisionRecord;
18use MediaWiki\Revision\SlotRecord;
19use RuntimeException;
20use stdClass;
21use TextContent;
22use TitleValue;
23use Traversable;
24use Wikimedia\Rdbms\IDatabase;
25
38class MessageCollection implements ArrayAccess, Iterator, Countable {
45 private const MAX_ITEMS_PER_QUERY = 2000;
46
48 public string $code;
49 private MessageDefinitions $definitions;
51 private array $infile = [];
52 // Keys and messages.
53
55 protected array $keys = [];
57 protected ?array $messages = [];
58 private ?array $reverseMap;
59 // Database resources
60
62 private Traversable $dbInfo;
64 private Traversable $dbData;
66 private Traversable $dbReviewData;
72 protected array $tags = [];
74 private array $authors = [];
75
80 public function __construct( string $code ) {
81 $this->code = $code;
82 }
83
89 public static function newFromDefinitions( MessageDefinitions $definitions, string $code ): self {
90 $collection = new self( $code );
91 $collection->definitions = $definitions;
92 $collection->resetForNewLanguage( $code );
93
94 return $collection;
95 }
96
97 public function getLanguage(): string {
98 return $this->code;
99 }
100
101 // Data setters
102
108 public function setInFile( array $messages ): void {
109 $this->infile = $messages;
110 }
111
117 public function setTags( string $type, array $keys ): void {
118 $this->tags[$type] = $keys;
119 }
120
125 public function keys(): array {
126 return $this->keys;
127 }
128
133 private function getTitles(): array {
134 return array_values( $this->keys );
135 }
136
141 public function getMessageKeys(): array {
142 return array_keys( $this->keys );
143 }
144
150 public function getTags( string $type ): array {
151 return $this->tags[$type] ?? [];
152 }
153
160 public function getAuthors(): array {
161 $this->loadTranslations();
162
163 $authors = array_flip( $this->authors );
164
165 foreach ( $this->messages as $m ) {
166 // Check if there are authors
168 $author = $m->getProperty( 'last-translator-text' );
169
170 if ( $author === null ) {
171 continue;
172 }
173
174 if ( !isset( $authors[$author] ) ) {
175 $authors[$author] = 1;
176 } else {
177 $authors[$author]++;
178 }
179 }
180
181 # arsort( $authors, SORT_NUMERIC );
182 ksort( $authors );
183 $fuzzyBot = FuzzyBot::getName();
184 $filteredAuthors = [];
185 foreach ( $authors as $author => $edits ) {
186 if ( $author !== $fuzzyBot ) {
187 $filteredAuthors[] = $author;
188 }
189 }
190
191 return $filteredAuthors;
192 }
193
199 public function addCollectionAuthors( array $authors, string $mode = 'append' ): void {
200 switch ( $mode ) {
201 case 'append':
202 $authors = array_merge( $this->authors, $authors );
203 break;
204 case 'set':
205 break;
206 default:
207 throw new InvalidArgumentException( "Invalid mode $mode" );
208 }
209
210 $this->authors = array_unique( $authors );
211 }
212
213 // Data modifiers
214
219 public function loadTranslations(): void {
220 // Performance optimization: Instead of building conditions based on key in every
221 // method, build them once and pass it on to each of them.
222 $dbr = Utilities::getSafeReadDB();
223 $titleConds = $this->getTitleConds( $dbr );
224
225 $this->loadData( $this->keys, $titleConds );
226 $this->loadInfo( $this->keys, $titleConds );
227 $this->loadReviewInfo( $this->keys, $titleConds );
228 $this->initMessages();
229 }
230
235 public function resetForNewLanguage( string $code ): void {
236 $this->code = $code;
237 $this->keys = $this->fixKeys();
238 $this->dbInfo = new EmptyIterator();
239 $this->dbData = new EmptyIterator();
240 $this->dbReviewData = new EmptyIterator();
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 } else {
269 $offset = (int)$offset;
270 }
271
272 // False means that cannot go back or forward
273 $backwardsOffset = $forwardsOffset = false;
274 // Backwards paging uses numerical indexes, see below
275
276 // Can only skip this if no offset has been provided or the
277 // offset is zero. (offset - limit ) > 1 does not work, because
278 // users can end in offest=2, limit=5 and can't see the first
279 // two messages. That's also why it is capped into zero with
280 // max(). And finally make the offsets to be strings even if
281 // they are numbers in this case.
282 if ( $offset > 0 ) {
283 $backwardsOffset = (string)( max( 0, $offset - $limit ) );
284 }
285
286 // Forwards paging uses keys. If user opens view Untranslated,
287 // translates some messages and then clicks next, the first
288 // message visible in the page is the first message not shown
289 // in the previous page (unless someone else translated it at
290 // the same time). If we used integer offsets, we would skip
291 // same number of messages that were translated, because they
292 // are no longer in the list. For backwards paging this is not
293 // such a big issue, so it still uses integer offsets, because
294 // we would need to also implement "direction" to have it work
295 // correctly.
296 if ( isset( $indexes[$offset + $limit] ) ) {
297 $forwardsOffset = $indexes[$offset + $limit];
298 }
299
300 $this->keys = array_slice( $this->keys, $offset, $limit, true );
301
302 return [ $backwardsOffset, $forwardsOffset, $offset ];
303 }
304
328 public function filter( string $type, bool $condition = true, ?int $value = null ): void {
329 if ( !in_array( $type, self::getAvailableFilters(), true ) ) {
330 throw new InvalidFilterException( $type );
331 }
332 $this->applyFilter( $type, $condition, $value );
333 }
334
335 private static function getAvailableFilters(): array {
336 return [
337 'fuzzy',
338 'optional',
339 'ignored',
340 'hastranslation',
341 'changed',
342 'translated',
343 'reviewer',
344 'last-translator',
345 ];
346 }
347
355 private function applyFilter( string $filter, bool $condition, ?int $value ): void {
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 RuntimeException( "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 private function filterOnCondition( array $keys, array $condKeys, bool $condition = true ): array {
418 if ( $condition ) {
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 private function filterFuzzy( array $keys, bool $condition ): array {
443 $this->loadInfo( $keys );
444
445 $origKeys = [];
446 if ( !$condition ) {
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 ) {
457 $keys = array_diff( $origKeys, $keys );
458 }
459
460 return $keys;
461 }
462
470 private function filterHastranslation( array $keys, bool $condition ): array {
471 $this->loadInfo( $keys );
472
473 $origKeys = [];
474 if ( !$condition ) {
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 ) {
489 $keys = array_diff( $origKeys, $keys );
490 }
491
492 return $keys;
493 }
494
503 private function filterChanged( array $keys, bool $condition ): array {
504 $this->loadData( $keys );
505
506 $origKeys = [];
507 if ( !$condition ) {
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 ) {
542 $keys = $this->filterOnCondition( $origKeys, $keys );
543 }
544
545 return $keys;
546 }
547
556 private function filterReviewer( array $keys, bool $condition, ?int $userId ): array {
557 $this->loadReviewInfo( $keys );
558 $origKeys = $keys;
559
560 /* This removes messages from the list which have certain
561 * reviewer (among others) */
562 foreach ( $this->dbReviewData as $row ) {
563 if ( $userId === null || (int)$row->trr_user === $userId ) {
564 unset( $keys[$this->rowToKey( $row )] );
565 }
566 }
567
568 if ( !$condition ) {
569 $keys = array_diff( $origKeys, $keys );
570 }
571
572 return $keys;
573 }
574
581 private function filterLastTranslator( array $keys, bool $condition, ?int $userId ): array {
582 $this->loadData( $keys );
583 $origKeys = $keys;
584
585 $userId = $userId ?? 0;
586 foreach ( $this->dbData as $row ) {
587 if ( (int)$row->rev_user === $userId ) {
588 unset( $keys[$this->rowToKey( $row )] );
589 }
590 }
591
592 if ( !$condition ) {
593 $keys = array_diff( $origKeys, $keys );
594 }
595
596 return $keys;
597 }
598
603 private function fixKeys(): array {
604 $newkeys = [];
605
606 $pages = $this->definitions->getPages();
607 foreach ( $pages as $key => $baseTitle ) {
608 $newkeys[$key] = new TitleValue(
609 $baseTitle->getNamespace(),
610 $baseTitle->getDBkey() . '/' . $this->code
611 );
612 }
613
614 return $newkeys;
615 }
616
622 private function loadInfo( array $keys, ?array $titleConds = null ): void {
623 if ( !$this->dbInfo instanceof EmptyIterator ) {
624 return;
625 }
626
627 if ( !count( $keys ) ) {
628 $this->dbInfo = new EmptyIterator();
629 return;
630 }
631
632 $dbr = Utilities::getSafeReadDB();
633 $tables = [ 'page', 'revtag' ];
634 $fields = [ 'page_namespace', 'page_title', 'rt_type' ];
635 $joins = [ 'revtag' =>
636 [
637 'LEFT JOIN',
638 [ 'page_id=rt_page', 'page_latest=rt_revision', 'rt_type' => RevTagStore::FUZZY_TAG ]
639 ]
640 ];
641
642 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
643 $iterator = new AppendIterator();
644 foreach ( $titleConds as $conds ) {
645 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
646 }
647
648 $this->dbInfo = $iterator;
649
650 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
651 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
652 // contain all the entries that are present in our $iterator and will throw notices.
653 $this->getReverseMap();
654 }
655
661 private function loadReviewInfo( array $keys, ?array $titleConds = null ): void {
662 if ( !$this->dbReviewData instanceof EmptyIterator ) {
663 return;
664 }
665
666 if ( !count( $keys ) ) {
667 $this->dbReviewData = new EmptyIterator();
668 return;
669 }
670
671 $dbr = Utilities::getSafeReadDB();
672 $tables = [ 'page', 'translate_reviews' ];
673 $fields = [ 'page_namespace', 'page_title', 'trr_user' ];
674 $joins = [ 'translate_reviews' =>
675 [
676 'JOIN',
677 [ 'page_id=trr_page', 'page_latest=trr_revision' ]
678 ]
679 ];
680
681 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
682 $iterator = new AppendIterator();
683 foreach ( $titleConds as $conds ) {
684 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
685 }
686
687 $this->dbReviewData = $iterator;
688
689 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
690 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
691 // contain all the entries that are present in our $iterator and will throw notices.
692 $this->getReverseMap();
693 }
694
700 private function loadData( array $keys, ?array $titleConds = null ): void {
701 if ( !$this->dbData instanceof EmptyIterator ) {
702 return;
703 }
704
705 if ( !count( $keys ) ) {
706 $this->dbData = new EmptyIterator();
707 return;
708 }
709
710 $dbr = Utilities::getSafeReadDB();
711 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
712 $revQuery = $revisionStore->getQueryInfo( [ 'page' ] );
713 $tables = $revQuery['tables'];
714 $fields = $revQuery['fields'];
715 $joins = $revQuery['joins'];
716
717 $titleConds = $titleConds ?? $this->getTitleConds( $dbr );
718 $iterator = new AppendIterator();
719 foreach ( $titleConds as $conds ) {
720 $conds = [ 'page_latest = rev_id', $conds ];
721 $iterator->append( $dbr->select( $tables, $fields, $conds, __METHOD__, [], $joins ) );
722 }
723
724 $this->dbData = $iterator;
725
726 // Populate and cache reverse map now, since if call to initMesages is delayed (e.g. a
727 // filter that calls loadData() is used, or ::slice is used) the reverse map will not
728 // contain all the entries that are present in our $iterator and will throw notices.
729 $this->getReverseMap();
730 }
731
736 private function getTitleConds( IDatabase $db ): array {
737 $titles = $this->getTitles();
738 $chunks = array_chunk( $titles, self::MAX_ITEMS_PER_QUERY );
739 $results = [];
740
741 foreach ( $chunks as $titles ) {
742 // Array of array( namespace, pagename )
743 $byNamespace = [];
744 foreach ( $titles as $title ) {
745 $namespace = $title->getNamespace();
746 $pagename = $title->getDBkey();
747 $byNamespace[$namespace][] = $pagename;
748 }
749
750 $conds = [];
751 foreach ( $byNamespace as $namespaces => $pagenames ) {
752 $cond = [
753 'page_namespace' => $namespaces,
754 'page_title' => $pagenames,
755 ];
756
757 $conds[] = $db->makeList( $cond, LIST_AND );
758 }
759
760 $results[] = $db->makeList( $conds, LIST_OR );
761 }
762
763 return $results;
764 }
765
771 private function rowToKey( stdClass $row ): ?string {
772 $map = $this->getReverseMap();
773 if ( isset( $map[$row->page_namespace][$row->page_title] ) ) {
774 return $map[$row->page_namespace][$row->page_title];
775 } else {
776 wfWarn( "Got unknown title from the database: {$row->page_namespace}:{$row->page_title}" );
777
778 return null;
779 }
780 }
781
783 private function getReverseMap(): array {
784 if ( isset( $this->reverseMap ) ) {
785 return $this->reverseMap;
786 }
787
788 $map = [];
790 foreach ( $this->keys as $mkey => $title ) {
791 $map[$title->getNamespace()][$title->getDBkey()] = $mkey;
792 }
793
794 $this->reverseMap = $map;
795 return $this->reverseMap;
796 }
797
802 public function initMessages(): void {
803 if ( $this->messages !== null ) {
804 return;
805 }
806
807 $messages = [];
808 $definitions = $this->definitions->getDefinitions();
809 $revStore = MediaWikiServices::getInstance()->getRevisionStore();
810 $queryFlags = Utilities::shouldReadFromPrimary() ? $revStore::READ_LATEST : 0;
811 foreach ( array_keys( $this->keys ) as $mkey ) {
812 $messages[$mkey] = new ThinMessage( $mkey, $definitions[$mkey] );
813 }
814
815 if ( !$this->dbData instanceof EmptyIterator ) {
816 $slotRows = $revStore->getContentBlobsForBatch(
817 $this->dbData, [ SlotRecord::MAIN ], $queryFlags
818 )->getValue();
819
820 foreach ( $this->dbData as $row ) {
821 $mkey = $this->rowToKey( $row );
822 if ( !isset( $messages[$mkey] ) ) {
823 continue;
824 }
825 $messages[$mkey]->setRow( $row );
826 $messages[$mkey]->setProperty( 'revision', $row->page_latest );
827
828 if ( isset( $slotRows[$row->rev_id][SlotRecord::MAIN] ) ) {
829 $slot = $slotRows[$row->rev_id][SlotRecord::MAIN];
830 $messages[$mkey]->setTranslation( $slot->blob_data );
831 }
832 }
833 }
834
835 $fuzzy = [];
836 foreach ( $this->dbInfo as $row ) {
837 if ( $row->rt_type !== null ) {
838 $fuzzy[] = $this->rowToKey( $row );
839 }
840 }
841
842 $this->setTags( 'fuzzy', $fuzzy );
843
844 // Copy tags if any.
845 foreach ( $this->tags as $type => $keys ) {
846 foreach ( $keys as $mkey ) {
847 if ( isset( $messages[$mkey] ) ) {
848 $messages[$mkey]->addTag( $type );
849 }
850 }
851 }
852
853 // Copy infile if any.
854 foreach ( $this->infile as $mkey => $value ) {
855 if ( isset( $messages[$mkey] ) ) {
856 $messages[$mkey]->setInfile( $value );
857 }
858 }
859
860 foreach ( $this->dbReviewData as $row ) {
861 $mkey = $this->rowToKey( $row );
862 if ( !isset( $messages[$mkey] ) ) {
863 continue;
864 }
865 $messages[$mkey]->appendProperty( 'reviewers', $row->trr_user );
866 }
867
868 // Set the status property
869 foreach ( $messages as $obj ) {
870 if ( $obj->hasTag( 'fuzzy' ) ) {
871 $obj->setProperty( 'status', 'fuzzy' );
872 } elseif ( is_array( $obj->getProperty( 'reviewers' ) ) ) {
873 $obj->setProperty( 'status', 'proofread' );
874 } elseif ( $obj->translation() !== null ) {
875 $obj->setProperty( 'status', 'translated' );
876 } else {
877 $obj->setProperty( 'status', 'untranslated' );
878 }
879 }
880
881 $this->messages = $messages;
882 }
883
888 public function offsetExists( $offset ): bool {
889 return isset( $this->keys[$offset] );
890 }
891
893 public function offsetGet( $offset ): ?Message {
894 return $this->messages[$offset] ?? null;
895 }
896
901 public function offsetSet( $offset, $value ): void {
902 $this->messages[$offset] = $value;
903 }
904
906 public function offsetUnset( $offset ): void {
907 unset( $this->keys[$offset] );
908 }
909
916 public function __get( string $name ): void {
917 throw new LogicException( __METHOD__ . ": Trying to access unknown property $name" );
918 }
919
925 public function __set( string $name, $value ): void {
926 throw new LogicException( __METHOD__ . ": Trying to modify unknown property $name" );
927 }
928
934 public function rewind(): void {
935 reset( $this->keys );
936 }
937
938 #[\ReturnTypeWillChange]
939 public function current() {
940 if ( !count( $this->keys ) ) {
941 return false;
942 }
943
944 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
945 return $this->messages[key( $this->keys )];
946 }
947
948 public function key(): ?string {
949 return key( $this->keys );
950 }
951
952 public function next(): void {
953 next( $this->keys );
954 }
955
956 public function valid(): bool {
957 return isset( $this->messages[key( $this->keys )] );
958 }
959
960 public function count(): int {
961 return count( $this->keys() );
962 }
963
965}
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: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:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getDBLoadBalancer(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->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:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore($services->getDBLoadBalancerFactory());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getDBLoadBalancer());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup());}, '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(), $services->get( 'Translate:RevTagStore'), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'),);}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnection(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getDBLoadBalancer());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
Class to manage revision tags for translatable bundles.
This file contains the class for core message collections implementation.
array $messages
array( Message String => Message, ... )
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.
array $keys
array( Message display key => database key, ... )
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 Messages (ThinMessage) 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.
Interface for message objects used by MessageCollection.
Definition Message.php:13
Message object which is based on database result row.
FuzzyBot - the misunderstood workhorse.
Definition FuzzyBot.php:15
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31