Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
SearchTranslationsSpecialPage.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TtmServer;
5
6use MediaWiki\Exception\ErrorPageError;
13use MediaWiki\Html\FormOptions;
14use MediaWiki\Html\Html;
15use MediaWiki\Json\FormatJson;
16use MediaWiki\Language\LanguageFactory;
17use MediaWiki\Logger\LoggerFactory;
18use MediaWiki\MainConfigNames;
19use MediaWiki\Message\Message;
20use MediaWiki\SpecialPage\SpecialPage;
21use MediaWiki\Title\Title;
22use MediaWiki\Utils\UrlUtils;
23use MediaWiki\WikiMap\WikiMap;
24use Psr\Log\LoggerInterface;
25
33class SearchTranslationsSpecialPage extends SpecialPage {
34 private FormOptions $opts;
41 private array $hl;
43 protected int $limit = 25;
44 private LoggerInterface $logger;
45
46 public function __construct(
47 private readonly TtmServerFactory $ttmServerFactory,
48 private readonly LanguageFactory $languageFactory,
49 private readonly UrlUtils $urlUtils,
50 ) {
51 parent::__construct( 'SearchTranslations' );
52 $this->hl = [
53 Utilities::getPlaceholder(),
54 Utilities::getPlaceholder(),
55 ];
56
57 $this->logger = LoggerFactory::getInstance( LogNames::MAIN );
58 }
59
61 public function execute( $subPage ) {
62 $this->setHeaders();
63 $this->checkPermissions();
64
65 $server = $this->ttmServerFactory->getDefaultForQuerying();
66 if ( !$server instanceof SearchableTtmServer ) {
67 throw new ErrorPageError( 'tux-sst-nosolr-title', 'tux-sst-nosolr-body' );
68 }
69
70 $out = $this->getOutput();
71 $out->addModuleStyles( 'jquery.uls.grid' );
72 $out->addModuleStyles( 'ext.translate.specialpages.styles' );
73 $out->addModuleStyles( 'ext.translate.special.translate.styles' );
74 $out->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
75 $out->addModules( 'ext.translate.special.searchtranslations' );
76 $out->addHelpLink( 'Help:Extension:Translate#searching' );
77
78 $this->opts = $opts = new FormOptions();
79 $opts->add( 'query', '' );
80 $opts->add( 'sourcelanguage', $this->getConfig()->get( MainConfigNames::LanguageCode ) );
81 $opts->add( 'language', '' );
82 $opts->add( 'group', '' );
83 $opts->add( 'grouppath', '' );
84 $opts->add( 'filter', '' );
85 $opts->add( 'match', '' );
86 $opts->add( 'case', '' );
87 $opts->add( 'limit', $this->limit );
88 $opts->add( 'offset', 0 );
89
90 $opts->fetchValuesFromRequest( $this->getRequest() );
91
92 $queryString = $opts->getValue( 'query' );
93 if ( $queryString === '' && $subPage !== null ) {
94 // When searching with subpage, its more likely that the user is searching with spaces
95 // rather than underscores
96 $queryString = str_replace( '_', ' ', $subPage );
97 $opts->setValue( 'query', $queryString );
98 }
99
100 if ( $queryString === '' ) {
101 $this->showEmptySearch();
102 return;
103 }
104
105 $search = $this->getSearchInput( $queryString );
106
107 $crossLanguageSearch = false;
108 $options = $params = $opts->getAllValues();
109 $filter = $opts->getValue( 'filter' );
110 try {
111 if ( $opts->getValue( 'language' ) === '' ) {
112 $options['language'] = $this->getLanguage()->getCode();
113 } elseif ( !Utilities::isSupportedLanguageCode( $options['language'] ) ) {
114 $this->showSearchError(
115 $search,
116 $this->msg( 'tux-sst-error-unsupported-language' )->plaintextParams( $options['language'] )
117 );
118 return;
119 }
120
121 $translationSearch = new CrossLanguageTranslationSearchQuery( $options, $server );
122 if ( in_array( $filter, $translationSearch->getAvailableFilters() ) ) {
123 if ( $options['language'] === $options['sourcelanguage'] ) {
124 $this->showSearchError( $search, $this->msg( 'tux-sst-error-language' ) );
125 return;
126 }
127
128 $opts->setValue( 'language', $options['language'] );
129 $documents = $translationSearch->getDocuments();
130 $total = $translationSearch->getTotalHits();
131 $resultSet = $translationSearch->getResultSet();
132
133 $crossLanguageSearch = true;
134 } else {
135 $resultSet = $server->search( $queryString, $params, $this->hl );
136 $documents = $server->getDocuments( $resultSet );
137 $total = $server->getTotalHits( $resultSet );
138 }
139 } catch ( TtmServerException $e ) {
140 $message = $e->getMessage();
141 // Known exceptions
142 if ( preg_match( '/^Result window is too large/', $message ) ) {
143 $this->showSearchError( $search, $this->msg( 'tux-sst-error-offset' ) );
144 return;
145 }
147 $this->showSearchError( $search, $this->msg( 'tux-sst-search-backend-error' ) );
148 $this->logger->warning(
149 'Translation search server failed: {exception}',
150 [ 'exception' => $e ]
151 );
152 return;
153 }
154
155 // Other exceptions
156 $this->logger->error(
157 'Translation search server unavailable: {exception}',
158 [ 'exception' => $e ]
159 );
160 throw new ErrorPageError( 'tux-sst-solr-offline-title', 'tux-sst-solr-offline-body' );
161 }
162
163 // Part 1: facets
164 $facets = $server->getFacets( $resultSet );
165 $facetHtml = '';
166
167 if ( $facets['language'] !== [] ) {
168 if ( $filter !== '' ) {
169 $facets['language'] = array_merge(
170 $facets['language'],
171 [ $opts->getValue( 'language' ) => $total ]
172 );
173 }
174 $facetHtml = Html::element( 'div',
175 [ 'class' => 'row facet languages',
176 'data-facets' => FormatJson::encode( $this->getLanguages( $facets['language'] ) ),
177 'data-language' => $opts->getValue( 'language' ),
178 ],
179 $this->msg( 'tux-sst-facet-language' )->text()
180 );
181 }
182
183 if ( $facets['group'] !== [] ) {
184 $facetHtml .= Html::element( 'div',
185 [ 'class' => 'row facet groups',
186 'data-facets' => FormatJson::encode( $this->getGroups( $facets['group'] ) ),
187 'data-group' => $opts->getValue( 'group' ) ],
188 $this->msg( 'tux-sst-facet-group' )->text()
189 );
190 }
191
192 // Part 2: results
193 $resultsHtml = '';
194
195 $title = Title::newFromText( $queryString );
196 if ( $title && !in_array( $filter, $translationSearch->getAvailableFilters() ) ) {
197 $handle = new MessageHandle( $title );
198 $code = $handle->getCode();
199 $language = $opts->getValue( 'language' );
200 if ( $code !== '' && $code !== $language && $handle->isValid() ) {
201 $dataProvider = new TranslationAidDataProvider( $handle );
202 $aid = new CurrentTranslationAid(
203 $handle->getGroup(),
204 $handle,
205 $this->getContext(),
206 $dataProvider
207 );
208 $document = [
209 'wiki' => WikiMap::getCurrentWikiId(),
210 'localid' => $handle->getTitleForBase()->getPrefixedText(),
211 'content' => $aid->getData()['value'],
212 'language' => $handle->getCode(),
213 ];
214 array_unshift( $documents, $document );
215 $total++;
216 }
217 }
218
219 foreach ( $documents as $document ) {
220 $text = $document['content'];
221 if ( $text === null ) {
222 continue;
223 }
224 $text = Utilities::convertWhiteSpaceToHTML( $text );
225
226 [ $pre, $post ] = $this->hl;
227 $text = str_replace( $pre, '<strong class="tux-search-highlight">', $text );
228 $text = str_replace( $post, '</strong>', $text );
229
230 $titleText = $document['localid'] . '/' . $document['language'];
231 $title = Title::newFromText( $titleText );
232 if ( !$title ) {
233 // Should not ever happen but who knows...
234 $this->logger->warning(
235 'SearchTranslationsSpecialPage: Invalid title: {title}',
236 [ 'title' => $titleText, 'document' => json_encode( $document ) ]
237 );
238 continue;
239 }
240
241 $resultAttribs = [
242 'class' => 'row tux-message',
243 'data-title' => $title->getPrefixedText(),
244 'data-language' => $document['language'],
245 ];
246
247 $handle = new MessageHandle( $title );
248
249 if ( $handle->isValid() ) {
250 $uri = Utilities::getEditorUrl( $handle, 'search' );
251 $link = Html::element(
252 'a',
253 [ 'href' => $uri ],
254 $this->msg( 'tux-sst-edit' )->text()
255 );
256 } else {
257 if ( $crossLanguageSearch ) {
258 $this->logger->warning(
259 'SearchTranslationsSpecialPage: Expected valid handle: {title}',
260 [ 'title' => $title->getPrefixedText() ]
261 );
262 continue;
263 }
264
265 $url = $this->urlUtils->parse( $document['uri'] );
266 if ( !$url ) {
267 continue;
268 }
269 $domain = $url['host'];
270 $link = Html::element(
271 'a',
272 [ 'href' => $document['uri'] ],
273 $this->msg( 'tux-sst-view-foreign', $domain )->text()
274 );
275 }
276
277 $access = Html::rawElement(
278 'div',
279 [ 'class' => 'row tux-edit tux-message-item' ],
280 $link
281 );
282
283 $titleText = $title->getPrefixedText();
284 $titleAttribs = [
285 'class' => 'row tux-title',
286 'dir' => 'ltr',
287 ];
288
289 $language = $this->languageFactory->getLanguage( $document['language'] );
290 $textAttribs = [
291 'class' => 'row tux-text',
292 'lang' => $language->getHtmlCode(),
293 'dir' => $language->getDir(),
294 ];
295
296 $resultsHtml .= Html::rawElement( 'div', $resultAttribs,
297 Html::rawElement( 'div', $textAttribs, $text )
298 . Html::element( 'div', $titleAttribs, $titleText )
299 . $access
300 );
301 }
302
303 $resultsHtml .= Html::rawElement( 'hr', [ 'class' => 'tux-pagination-line' ] );
304
305 $prev = $next = '';
306 $offset = $this->opts->getValue( 'offset' );
307 $params = $this->opts->getChangedValues();
308
309 if ( $total - $offset > $this->limit ) {
310 $newParams = [ 'offset' => $offset + $this->limit ] + $params;
311 $attribs = [
312 'class' => 'cdx-button cdx-button--fake-button cdx-button--fake-button--enabled pager-next',
313 'href' => $this->getPageTitle()->getLocalURL( $newParams ),
314 ];
315 $next = Html::element( 'a', $attribs, $this->msg( 'tux-sst-next' )->text() );
316 }
317 if ( $offset ) {
318 $newParams = [ 'offset' => max( 0, $offset - $this->limit ) ] + $params;
319 $attribs = [
320 'class' => 'cdx-button cdx-button--fake-button cdx-button--fake-button--enabled pager-prev',
321 'href' => $this->getPageTitle()->getLocalURL( $newParams ),
322 ];
323 $prev = Html::element( 'a', $attribs, $this->msg( 'tux-sst-prev' )->text() );
324 }
325
326 $resultsHtml .= Html::rawElement( 'div', [ 'class' => 'tux-pagination-links' ],
327 "$prev $next"
328 );
329
330 $count = $this->msg( 'tux-sst-count' )->numParams( $total )->escaped();
331
332 $this->showSearch( $search, $count, $facetHtml, $resultsHtml, $total );
333 }
334
335 private function getLanguages( array $facet ): array {
336 $output = [];
337
338 $nonDefaults = $this->opts->getChangedValues();
339 $selected = $this->opts->getValue( 'language' );
340 $filter = $this->opts->getValue( 'filter' );
341
342 foreach ( $facet as $key => $value ) {
343 if ( $filter !== '' && $key === $selected ) {
344 unset( $nonDefaults['language'] );
345 unset( $nonDefaults['filter'] );
346 } elseif ( $filter !== '' ) {
347 $nonDefaults['language'] = $key;
348 $nonDefaults['filter'] = $filter;
349 } elseif ( $key === $selected ) {
350 unset( $nonDefaults['language'] );
351 } else {
352 $nonDefaults['language'] = $key;
353 }
354
355 $url = $this->getPageTitle()->getLocalURL( $nonDefaults );
356 $value = $this->getLanguage()->formatNum( $value );
357
358 $output[$key] = [
359 'count' => $value,
360 'url' => $url
361 ];
362 }
363
364 return $output;
365 }
366
367 private function getGroups( array $facet ): array {
368 $structure = MessageGroups::getGroupStructure();
369 return $this->makeGroupFacetRows( $structure, $facet );
370 }
371
372 private function makeGroupFacetRows(
373 array $groups,
374 array $counts,
375 int $level = 0,
376 string $pathString = ''
377 ): array {
378 $output = [];
379
380 $nonDefaults = $this->opts->getChangedValues();
381 $selected = $this->opts->getValue( 'group' );
382 $path = explode( '|', $this->opts->getValue( 'grouppath' ) );
383
384 foreach ( $groups as $mixed ) {
385 $subgroups = $group = $mixed;
386
387 if ( is_array( $mixed ) ) {
388 $group = array_shift( $subgroups );
389 } else {
390 $subgroups = [];
391 }
392 '@phan-var \MessageGroup $group';
393 $id = $group->getId();
394
395 if ( $id !== $selected && !isset( $counts[$id] ) ) {
396 continue;
397 }
398
399 if ( $id === $selected ) {
400 unset( $nonDefaults['group'] );
401 $nonDefaults['grouppath'] = $pathString;
402 } else {
403 $nonDefaults['group'] = $id;
404 $nonDefaults['grouppath'] = $pathString . $id;
405 }
406
407 $value = $counts[$id] ?? 0;
408
409 $output[$id] = [
410 'id' => $id,
411 'count' => $value,
412 'label' => $group->getLabel(),
413 ];
414
415 if ( isset( $path[$level] ) && $path[$level] === $id ) {
416 $output[$id]['groups'] = $this->makeGroupFacetRows(
417 $subgroups,
418 $counts,
419 $level + 1,
420 "$pathString$id|"
421 );
422 }
423 }
424
425 return $output;
426 }
427
428 private function showSearch(
429 string $search,
430 string $count,
431 string $facets,
432 string $results,
433 int $total
434 ): void {
435 $messageSelector = $this->messageSelector();
436 $this->getOutput()->addHTML(
437 <<<HTML
438 <div class="grid tux-searchpage">
439 <div class="row tux-searchboxform">
440 <div class="tux-search-tabs offset-by-three">$messageSelector</div>
441 <div class="row tux-search-options">
442 <div class="offset-by-three nine columns tux-search-inputs">
443 <div class="row searchinput">$search</div>
444 <div class="row count">$count</div>
445 </div>
446 </div>
447 </div>
448 HTML
449 );
450
451 $query = trim( $this->opts->getValue( 'query' ) );
452 $hasSpace = preg_match( '/\s/', $query );
453 $match = $this->opts->getValue( 'match' );
454 $size = 100;
455 if ( $total > $size && $match !== 'all' && $hasSpace ) {
456 $params = $this->opts->getChangedValues();
457 $params = [ 'match' => 'all' ] + $params;
458 $linkText = $this->msg( 'tux-sst-link-all-match' )->text();
459 $link = $this->getPageTitle()->getFullURL( $params );
460 $link = "<span class='plainlinks'>[$link $linkText]</span>";
461
462 $out = $this->getOutput();
463 $out->addHTML(
464 Html::successBox(
465 $out->msg( 'tux-sst-match-message', $link )->parse()
466 )
467 );
468 }
469
470 $this->getOutput()->addHTML(
471 <<<HTML
472 <div class="row searchcontent">
473 <div class="three columns facets">$facets</div>
474 <div class="nine columns results">$results</div>
475 </div>
476 </div>
477 HTML
478 );
479 }
480
481 private function showEmptySearch(): void {
482 $search = $this->getSearchInput( '' );
483 $this->getOutput()->addHTML(
484 <<<HTML
485 <div class="grid tux-searchpage">
486 <div class="row searchinput">
487 <div class="nine columns offset-by-three">$search</div>
488 </div>
489 </div>
490 HTML
491 );
492 }
493
494 private function showSearchError( string $search, Message $message ): void {
495 $messageSelector = $this->messageSelector();
496 $messageHTML = Html::errorBox(
497 $message->parse(),
498 '',
499 'row'
500 );
501 $this->getOutput()->addHTML(
502 <<<HTML
503 <div class="grid tux-searchpage">
504 <div class="row tux-searchboxform">
505 <div class="tux-search-tabs offset-by-three">$messageSelector</div>
506 <div class="row tux-search-options">
507 <div class="offset-by-three nine columns tux-search-inputs">
508 <div class="row searchinput">$search</div>
509 $messageHTML
510 </div>
511 </div>
512 </div>
513 </div>
514 HTML
515 );
516 }
517
519 private function ellipsisSelector( string $key, string $value ): string {
520 $nonDefaults = $this->opts->getChangedValues();
521 $taskParams = [ 'filter' => $value ] + $nonDefaults;
522 ksort( $taskParams );
523 $href = $this->getPageTitle()->getLocalURL( $taskParams );
524 $link = Html::element( 'a',
525 [ 'href' => $href ],
526 // Messages for grepping:
527 // tux-sst-ellipsis-untranslated
528 // tux-sst-ellipsis-outdated
529 $this->msg( 'tux-sst-ellipsis-' . $key )->text()
530 );
531
532 return Html::rawElement( 'li', [
533 'class' => 'column',
534 'data-filter' => $value,
535 'data-title' => $key,
536 ], $link );
537 }
538
540 private function messageSelector(): string {
541 $nonDefaults = $this->opts->getChangedValues();
542 $output = Html::openElement( 'div', [ 'class' => 'row tux-messagetable-header' ] );
543 $output .= Html::openElement( 'div', [ 'class' => 'twelve columns' ] );
544 $output .= Html::openElement( 'ul', [ 'class' => 'row tux-message-selector' ] );
545 $tabs = [
546 'default' => '',
547 'translated' => 'translated',
548 'untranslated' => 'untranslated'
549 ];
550
551 $ellipsisOptions = [
552 'outdated' => 'fuzzy'
553 ];
554
555 $selected = $this->opts->getValue( 'filter' );
556 if ( in_array( $selected, $ellipsisOptions ) ) {
557 $ellipsisOptions = array_slice( $tabs, -1 );
558
559 // Remove the last tab
560 array_pop( $tabs );
561 $tabs = array_merge( $tabs, [ 'outdated' => $selected ] );
562 } elseif ( !in_array( $selected, $tabs ) ) {
563 $selected = '';
564 }
565
566 $container = Html::openElement( 'ul', [ 'class' => 'column tux-message-selector' ] );
567 foreach ( $ellipsisOptions as $optKey => $optValue ) {
568 $container .= $this->ellipsisSelector( $optKey, $optValue );
569 }
570
571 $sourceLanguage = $this->opts->getValue( 'sourcelanguage' );
572 $sourceLanguage = Utilities::getLanguageName( $sourceLanguage );
573 foreach ( $tabs as $tab => $filter ) {
574 // Messages for grepping:
575 // tux-sst-default
576 // tux-sst-translated
577 // tux-sst-untranslated
578 // tux-sst-outdated
579 $tabClass = "tux-sst-$tab";
580 $taskParams = [ 'filter' => $filter ] + $nonDefaults;
581 ksort( $taskParams );
582 $href = $this->getPageTitle()->getLocalURL( $taskParams );
583 if ( $tab === 'default' ) {
584 $link = Html::element(
585 'a',
586 [ 'href' => $href ],
587 $this->msg( $tabClass )->text()
588 );
589 } else {
590 $link = Html::element(
591 'a',
592 [ 'href' => $href ],
593 $this->msg( $tabClass, $sourceLanguage )->text()
594 );
595 }
596
597 if ( $selected === $filter ) {
598 $tabClass .= ' selected';
599 }
600 $output .= Html::rawElement( 'li', [
601 'class' => [ 'column', $tabClass ],
602 'data-filter' => $filter,
603 'data-title' => $tab,
604 ], $link );
605 }
606
607 // More column
608 $output .= Html::rawElement( 'li', [ 'class' => 'column more' ], '...' . $container );
609 $output .= Html::closeElement( 'ul' ) . Html::closeElement( 'div' ) . Html::closeElement( 'div' );
610
611 return $output;
612 }
613
614 private function getSearchInput( string $query ): string {
615 $attribs = [
616 'placeholder' => $this->msg( 'tux-sst-search-ph' )->text(),
617 'class' => 'searchinputbox cdx-text-input__input',
618 'dir' => $this->getLanguage()->getDir()
619 ];
620
621 $title = Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
622
623 // 1. Text input
624 $textInput = Html::rawElement(
625 'div',
626 [ 'class' => 'cdx-text-input cdx-text-input--has-start-icon' ],
627 Html::input( 'query', $query, 'search', $attribs ) .
628 Html::element( 'span', [ 'class' => 'cdx-text-input__icon cdx-text-input__start-icon' ] )
629 );
630
631 // 2. Input wrapper
632 $inputWrapper = Html::rawElement(
633 'div',
634 [ 'class' => 'cdx-search-input__input-wrapper' ],
635 $textInput
636 );
637
638 // 3. Search button
639 $searchButton = Html::element(
640 'button',
641 [
642 'class' => 'cdx-button cdx-search-input__end-button ' .
643 'cdx-button--action-progressive cdx-button--weight-primary',
644 'type' => 'submit',
645 ],
646 $this->msg( 'tux-sst-search' )->text()
647 );
648
649 // 4. Integrated search input
650 $searchInput = Html::rawElement(
651 'div',
652 [ 'class' => 'cdx-search-input cdx-search-input--has-end-button' ],
653 $inputWrapper . $searchButton
654 );
655
656 // 5. Wrap everything in a field
657 $field = Html::rawElement(
658 'div',
659 [ 'class' => 'cdx-field' ],
660 Html::rawElement( 'div', [ 'class' => 'cdx-field__control' ], $searchInput )
661 . Html::rawElement(
662 'div',
663 [ 'class' => 'cdx-field__help-text' ],
664 $this->msg( 'tux-sst-search-info' )->parse()
665 )
666 );
667
668 $nonDefaults = $this->opts->getChangedValues();
669 $checkbox = Html::element( 'input', [
670 'type' => 'checkbox',
671 'name' => 'case',
672 'class' => 'cdx-checkbox__input',
673 'value' => '1',
674 'id' => 'tux-case-sensitive',
675 'checked' => isset( $nonDefaults['case'] ),
676 ] );
677 $checkLabel = Html::rawElement(
678 'div',
679 [ 'class' => 'cdx-checkbox' ],
680 Html::rawElement(
681 'div',
682 [ 'class' => 'cdx-checkbox__wrapper' ],
683 $checkbox
684 . Html::element( 'span', [ 'class' => 'cdx-checkbox__icon' ] )
685 . Html::rawElement(
686 'div',
687 [ 'class' => 'cdx-checkbox__label cdx-label' ],
688 Html::rawElement(
689 'label',
690 [ 'class' => 'cdx-label__label', 'for' => 'tux-case-sensitive' ],
691 Html::element(
692 'span',
693 [ 'class' => 'cdx-label__label__text' ],
694 $this->msg( 'tux-sst-case-sensitive' )->text()
695 )
696 )
697 )
698 )
699 );
700
701 $lang = $this->getRequest()->getVal( 'language' );
702 $language = $lang === null ? '' : Html::hidden( 'language', $lang );
703
704 return Html::rawElement(
705 'form',
706 [ 'action' => wfScript(), 'name' => 'searchform' ],
707 $title . $field . $checkLabel . $language
708 );
709 }
710
711 protected function getGroupName(): string {
712 return 'translation';
713 }
714}
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(), $services->getContentLanguageCode() ->toString(), 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 { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} 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(), $services->get( 'Translate:HookRunner'),);}, '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->getConnectionProvider());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);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@phpcs-require-sorted-array
Constants for log channel names used in this extension.
Definition LogNames.php:13
const MAIN
Default log channel for the extension.
Definition LogNames.php:15
Factory class for accessing message groups individually by id or all of them as a list.
Class for pointing to messages, like Title class is for titles.
Translation aid that provides the current saved translation.
Class to handle TtmServer specific exceptions.
const TRANSIENT_SEARCH_BACKEND_FAILURE
Exception code for transient errors when contacting the search backend.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:30
Interface for TtmServer that can act as backend for translation search.