53 private const GROUP_SYNC_INFO_WRAPPER_CLASS =
'smg-group-sync-cache-info';
54 private const RIGHT =
'translate-manage';
60 protected $hasRight =
false;
68 private $synchronizationCache;
70 private $displayGroupSyncInfo;
72 private $jobQueueGroup;
74 private $messageIndex;
76 private $linkBatchFactory;
78 public function __construct(
80 NamespaceInfo $nsInfo,
81 RevisionLookup $revLookup,
83 JobQueueGroup $jobQueueGroup,
85 LinkBatchFactory $linkBatchFactory
88 parent::__construct(
'ManageMessageGroups' );
89 $this->contLang = $contLang;
90 $this->nsInfo = $nsInfo;
91 $this->revLookup = $revLookup;
92 $this->synchronizationCache = $synchronizationCache;
94 $this->jobQueueGroup = $jobQueueGroup;
95 $this->messageIndex = $messageIndex;
96 $this->linkBatchFactory = $linkBatchFactory;
99 public function doesWrites() {
103 protected function getGroupName() {
104 return 'translation';
107 public function getDescription() {
108 return $this->msg(
'managemessagegroups' );
111 public function execute( $par ) {
114 $out = $this->getOutput();
115 $out->addModuleStyles(
'ext.translate.specialpages.styles' );
116 $out->addModules(
'ext.translate.special.managegroups' );
117 $out->addHelpLink(
'Help:Extension:Translate/Group_management' );
119 $name = $par ?: MessageChangeStorage::DEFAULT_NAME;
121 $this->cdb = MessageChangeStorage::getCdbPath( $name );
122 if ( !MessageChangeStorage::isValidCdbName( $name ) || !file_exists( $this->cdb ) ) {
123 if ( $this->getConfig()->
get(
'TranslateGroupSynchronizationCache' ) ) {
125 $this->displayGroupSyncInfo->getGroupsInSyncHtml(
126 $this->synchronizationCache->getGroupsInSync(),
127 self::GROUP_SYNC_INFO_WRAPPER_CLASS
132 $this->displayGroupSyncInfo->getHtmlForGroupsWithError(
133 $this->synchronizationCache,
134 self::GROUP_SYNC_INFO_WRAPPER_CLASS,
142 $out->addWikiMsg(
'translate-smg-nochanges' );
147 $user = $this->getUser();
148 $this->hasRight = $user->isAllowed( self::RIGHT );
150 $req = $this->getRequest();
151 if ( !$req->wasPosted() ) {
152 $this->showChanges( $this->
getLimit() );
157 $block = $user->getBlock();
158 if ( $block && $block->isSitewide() ) {
159 throw new UserBlockedError(
162 $this->getLanguage(),
167 $csrfTokenSet = $this->getContext()->getCsrfTokenSet();
168 if ( !$this->hasRight || !$csrfTokenSet->matchTokenField(
'token' ) ) {
169 throw new PermissionsError( self::RIGHT );
172 $this->processSubmit();
179 ini_get(
'max_input_vars' ),
180 ini_get(
'suhosin.post.max_vars' ),
181 ini_get(
'suhosin.request.max_vars' )
184 $limits = array_filter( $limits );
185 return (
int)min( $limits );
188 protected function getLegend(): string {
189 $text = $this->diff->addHeader(
191 $this->msg(
'translate-smg-left' )->escaped(),
192 $this->msg(
'translate-smg-right' )->escaped()
195 return Html::rawElement(
'div', [
'class' =>
'mw-translate-smg-header' ], $text );
198 protected function showChanges(
int $limit ): void {
199 $diff = new DifferenceEngine( $this->getContext() );
200 $diff->showDiffStyle();
201 $diff->setReducedLineNumbers();
204 $out = $this->getOutput();
206 Html::openElement(
'form', [
'method' =>
'post' ] ) .
207 Html::hidden(
'title', $this->getPageTitle()->getPrefixedText(), [
208 'id' =>
'smgPageTitle'
210 Html::hidden(
'token', $this->getContext()->getCsrfTokenSet()->getToken() ) .
211 Html::hidden(
'changesetModifiedTime',
212 MessageChangeStorage::getLastModifiedTime( $this->cdb ) ) .
219 $groupSyncCacheEnabled = $this->getConfig()->get(
'TranslateGroupSynchronizationCache' );
220 if ( $groupSyncCacheEnabled ) {
222 $this->displayGroupSyncInfo->getGroupsInSyncHtml(
223 $this->synchronizationCache->getGroupsInSync(),
224 self::GROUP_SYNC_INFO_WRAPPER_CLASS
229 $this->displayGroupSyncInfo->getHtmlForGroupsWithError(
230 $this->synchronizationCache,
231 self::GROUP_SYNC_INFO_WRAPPER_CLASS,
237 $reader = \Cdb\Reader::open( $this->cdb );
238 $groups = $this->getGroupsFromCdb( $reader );
239 foreach ( $groups as $id => $group ) {
240 $sourceChanges = MessageSourceChange::loadModifications(
241 Utilities::deserialize( $reader->get( $id ) )
243 $out->addHTML( Html::element(
'h2', [], $group->getLabel() ) );
245 if ( $groupSyncCacheEnabled && $this->synchronizationCache->groupHasErrors( $id ) ) {
247 Html::warningBox( $this->msg(
'translate-smg-group-sync-error-warn' )->escaped(),
'center' )
252 $lb = $this->linkBatchFactory->newLinkBatch();
253 $ns = $group->getNamespace();
254 $isCap = $this->nsInfo->isCapitalized( $ns );
255 $languages = $sourceChanges->getLanguages();
257 foreach ( $languages as $language ) {
258 $languageChanges = $sourceChanges->getModificationsForLanguage( $language );
259 foreach ( $languageChanges as $type => $changes ) {
260 foreach ( $changes as $params ) {
262 $key = $params[
'key'];
264 $key = $this->contLang->ucfirst( $key );
266 $lb->add( $ns,
"$key/$language" );
272 foreach ( $languages as $language ) {
275 $changes[ MessageSourceChange::ADDITION ] = $sourceChanges->getAdditions( $language );
276 $changes[ MessageSourceChange::DELETION ] = $sourceChanges->getDeletions( $language );
277 $changes[ MessageSourceChange::CHANGE ] = $sourceChanges->getChanges( $language );
279 foreach ( $changes as $type => $messages ) {
280 foreach ( $messages as $params ) {
281 $change = $this->formatChange( $group, $sourceChanges, $language, $type, $params, $limit );
282 $out->addHTML( $change );
287 $out->wrapWikiMsg(
"<div class=warning>\n$1\n</div>",
'translate-smg-more' );
294 $this->showRenames( $group, $sourceChanges, $out, $language, $limit );
299 $button =
new ButtonInputWidget( [
301 'label' => $this->msg(
'translate-smg-submit' )->plain(),
302 'disabled' => !$this->hasRight ?
'disabled' : null,
303 'classes' => [
'mw-translate-smg-submit' ],
304 'title' => !$this->hasRight ? $this->msg(
'translate-smg-notallowed' )->plain() : null,
305 'flags' => [
'primary',
'progressive' ],
307 $out->addHTML( $button );
308 $out->addHTML( Html::closeElement(
'form' ) );
311 protected function formatChange(
313 MessageSourceChange $changes,
319 $key = $params[
'key'];
320 $title = Title::makeTitleSafe( $group->
getNamespace(),
"$key/$language" );
321 $id = self::changeId( $group->
getId(), $language, $type, $key );
323 $isReusedKey =
false;
325 if ( $title && $type ===
'addition' && $title->exists() ) {
334 $noticeHtml .= Html::warningBox( $this->msg(
'translate-manage-key-reused' )->text() );
336 } elseif ( $title && ( $type ===
'deletion' || $type ===
'change' ) && !$title->exists() ) {
343 $titleLink = $this->getLinkRenderer()->makeLink( $title );
345 if ( $type ===
'deletion' ) {
346 $revTitle = $this->revLookup->getRevisionByTitle( $title );
348 wfWarn(
"[ManageGroupSpecialPage] No revision associated with {$title->getPrefixedText()}" );
350 $content = $revTitle ? $revTitle->getContent( SlotRecord::MAIN ) :
null;
351 $wiki = ( $content instanceof TextContent ) ? $content->getText() :
'';
353 if ( $wiki ===
'' ) {
354 $noticeHtml .= Html::warningBox(
355 $this->msg(
'translate-manage-empty-content' )->text()
359 $oldContent = ContentHandler::makeContent( (
string)$wiki, $title );
360 $newContent = ContentHandler::makeContent(
'', $title );
361 $this->diff->setContent( $oldContent, $newContent );
362 $text = $this->diff->getDiff( $titleLink,
'', $noticeHtml );
363 } elseif ( $type ===
'addition' ) {
366 if ( $sourceLanguage === $language ) {
367 if ( $this->hasRight ) {
368 $menu = Html::rawElement(
371 'class' =>
'smg-rename-actions',
373 'data-group-id' => $group->
getId(),
374 'data-lang' => $language,
375 'data-msgkey' => $key,
376 'data-msgtitle' => $title->getFullText()
381 } elseif ( !self::isMessageDefinitionPresent( $group, $changes, $key ) ) {
382 $noticeHtml .= Html::warningBox(
383 $this->msg(
'translate-manage-source-message-not-found' )->text(),
384 'mw-translate-smg-notice-important'
388 $menu = Html::hidden(
"msg/$id",
'ignore', [
'id' =>
"i/$id" ] );
392 if ( $params[
'content'] ===
'' ) {
393 $noticeHtml .= Html::warningBox(
394 $this->msg(
'translate-manage-empty-content' )->text()
398 $oldContent = ContentHandler::makeContent(
'', $title );
399 $newContent = ContentHandler::makeContent( (
string)$params[
'content'], $title );
400 $this->diff->setContent( $oldContent, $newContent );
401 $text = $this->diff->getDiff(
'', $titleLink . $menu, $noticeHtml );
402 } elseif ( $type ===
'change' ) {
403 $wiki = Utilities::getContentForTitle( $title,
true );
410 $shouldFuzzy = $sourceLanguage === $language && $wiki !== $params[
'content'];
412 if ( $sourceLanguage === $language ) {
413 $label = $this->msg(
'translate-manage-action-fuzzy' )->text();
414 $actions .= Xml::radioLabel( $label,
"msg/$id",
"fuzzy",
"f/$id", $shouldFuzzy );
418 $sourceLanguage !== $language &&
420 !self::isMessageDefinitionPresent( $group, $changes, $key )
422 $noticeHtml .= Html::warningBox(
423 $this->msg(
'translate-manage-source-message-not-found' )->text(),
424 'mw-translate-smg-notice-important'
428 $actions .= Html::hidden(
"msg/$id",
'ignore', [
'id' =>
"i/$id" ] );
431 $label = $this->msg(
'translate-manage-action-import' )->text();
432 $actions .= Xml::radioLabel( $label,
"msg/$id",
"import",
"imp/$id", !$shouldFuzzy );
434 $label = $this->msg(
'translate-manage-action-ignore' )->text();
435 $actions .= Xml::radioLabel( $label,
"msg/$id",
"ignore",
"i/$id" );
439 $oldContent = ContentHandler::makeContent( (
string)$wiki, $title );
440 $newContent = ContentHandler::makeContent( (
string)$params[
'content'], $title );
442 $this->diff->setContent( $oldContent, $newContent );
443 $text .= $this->diff->getDiff( $titleLink, $actions, $noticeHtml );
446 $hidden = Html::hidden( $id, 1 );
449 $classes =
"mw-translate-smg-change smg-change-$type";
456 return Html::rawElement(
'div', [
'class' => $classes ], $text );
459 protected function processSubmit(): void {
460 $req = $this->getRequest();
461 $out = $this->getOutput();
464 $modificationJobs = $renameJobData = [];
465 $lastModifiedTime = intval( $req->getVal(
'changesetModifiedTime' ) );
467 if ( !MessageChangeStorage::isModifiedSince( $this->cdb, $lastModifiedTime ) ) {
468 $out->addWikiMsg(
'translate-smg-changeset-modified' );
472 $reader = \Cdb\Reader::open( $this->cdb );
473 $groups = $this->getGroupsFromCdb( $reader );
474 $groupSyncCacheEnabled = $this->getConfig()->get(
'TranslateGroupSynchronizationCache' );
477 foreach ( $groups as $groupId => $group ) {
480 throw new RuntimeException(
"Expected $groupId to be FileBasedMessageGroup, got "
481 . get_class( $group )
485 $changes = Utilities::deserialize( $reader->get( $groupId ) );
486 if ( $groupSyncCacheEnabled && $this->synchronizationCache->groupHasErrors( $groupId ) ) {
487 $postponed[$groupId] = $changes;
491 $sourceChanges = MessageSourceChange::loadModifications( $changes );
492 $groupModificationJobs = [];
493 $groupRenameJobData = [];
494 $languages = $sourceChanges->getLanguages();
495 foreach ( $languages as $language ) {
497 $this->handleModificationsSubmit(
503 $groupModificationJobs
507 $this->handleRenameSubmit(
514 $groupModificationJobs
517 if ( !isset( $postponed[$groupId][$language] ) ) {
518 $group->getMessageGroupCache( $language )->create();
522 if ( $groupSyncCacheEnabled && !isset( $postponed[ $groupId ] ) ) {
523 $this->synchronizationCache->markGroupAsReviewed( $groupId );
526 $modificationJobs[$groupId] = $groupModificationJobs;
527 $renameJobData[$groupId] = $groupRenameJobData;
528 }
catch ( Exception $e ) {
530 "ManageGroupsSpecialPage: Error in processSubmit. Group: $groupId\n" .
534 $errorGroups[] = $group->
getLabel();
538 $renameJobs = $this->createRenameJobs( $renameJobData );
539 $this->startSync( $modificationJobs, $renameJobs );
542 rename( $this->cdb, $this->cdb .
'-' . wfTimestamp() );
544 if ( $errorGroups ) {
545 $errorMsg = $this->getProcessingErrorMessage( $errorGroups, count( $groups ) );
549 'mw-translate-smg-submitted'
554 if ( count( $postponed ) ) {
555 $postponedSourceChanges = [];
556 foreach ( $postponed as $groupId => $changes ) {
557 $postponedSourceChanges[$groupId] = MessageSourceChange::loadModifications( $changes );
561 $this->showChanges( $this->getLimit() );
562 } elseif ( $errorGroups === [] ) {
563 $out->addWikiMsg(
'translate-smg-submitted' );
567 protected static function changeId(
573 return 'smg/' . substr( sha1(
"$groupId/$language/$type/$key" ), 0, 7 );
580 public static function tabify( Skin $skin, array &$tabs ): void {
581 $title = $skin->getTitle();
582 if ( !$title->isSpecialPage() ) {
585 $specialPageFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
586 [ $alias, ] = $specialPageFactory->resolveAlias( $title->getText() );
589 'ManageMessageGroups' =>
'namespaces',
590 'AggregateGroups' =>
'namespaces',
591 'SupportedLanguages' =>
'views',
592 'TranslationStats' =>
'views',
594 if ( !isset( $pagesInGroup[$alias] ) ) {
598 $tabs[
'namespaces'] = [];
599 foreach ( $pagesInGroup as $spName => $section ) {
600 $spClass = $specialPageFactory->getPage( $spName );
602 if ( $spClass ===
null || $spClass instanceof DisabledSpecialPage ) {
605 $spTitle = $spClass->getPageTitle();
607 $tabs[$section][strtolower( $spName )] = [
608 'text' => $spClass->getDescription(),
609 'href' => $spTitle->getLocalURL(),
610 'class' => $alias === $spName ?
'selected' :
'',
619 private static function isMessageDefinitionPresent(
621 MessageSourceChange $changes,
624 $sourceLanguage = $group->getSourceLanguage();
625 if ( $changes->findMessage( $sourceLanguage, $msgKey, [ MessageSourceChange::ADDITION ] ) ) {
630 $sourceHandle =
new MessageHandle( Title::makeTitle( $namespace, $msgKey ) );
631 return $sourceHandle->isValid();
634 private function showRenames(
636 MessageSourceChange $sourceChanges,
641 $changes = $sourceChanges->getRenames( $language );
642 foreach ( $changes as $key => $params ) {
645 if ( !isset( $changes[ $key ] ) ) {
650 $sourceChanges->isEqual( $language, $key ) ) {
658 $secondKey = $sourceChanges->getMatchedKey( $language, $key ) ??
'';
659 $secondMsg = $sourceChanges->getMatchedMessage( $language, $key );
660 if ( $secondMsg ===
null ) {
661 throw new RuntimeException(
"Could not find matched message for $key" );
665 $sourceChanges->isPreviousState(
668 [ MessageSourceChange::ADDITION, MessageSourceChange::CHANGE ]
671 $addedMsg = $firstMsg;
672 $deletedMsg = $secondMsg;
674 $addedMsg = $secondMsg;
675 $deletedMsg = $firstMsg;
678 $change = $this->formatRename(
683 $sourceChanges->isEqual( $language, $key ),
686 $out->addHTML( $change );
689 unset( $changes[$secondKey] );
694 $out->wrapWikiMsg(
"<div class=warning>\n$1\n</div>",
'translate-smg-more' );
700 private function formatRename(
708 $addedKey = $addedMsg[
'key'];
709 $deletedKey = $deletedMsg[
'key'];
712 $addedTitle = Title::makeTitleSafe( $group->
getNamespace(),
"$addedKey/$language" );
713 $deletedTitle = Title::makeTitleSafe( $group->
getNamespace(),
"$deletedKey/$language" );
714 $id = self::changeId( $group->
getId(), $language, MessageSourceChange::RENAME, $addedKey );
716 $addedTitleLink = $this->getLinkRenderer()->makeLink( $addedTitle );
717 $deletedTitleLink = $this->getLinkRenderer()->makeLink( $deletedTitle );
719 $renameSelected =
true;
722 $renameSelected =
false;
723 $label = $this->msg(
'translate-manage-action-rename-fuzzy' )->text();
724 $actions .= Xml::radioLabel( $label,
"msg/$id",
"renamefuzzy",
"rf/$id",
true );
727 $label = $this->msg(
'translate-manage-action-rename' )->text();
728 $actions .= Xml::radioLabel( $label,
"msg/$id",
"rename",
"imp/$id", $renameSelected );
730 $label = $this->msg(
'translate-manage-action-import' )->text();
731 $actions .= Xml::radioLabel( $label,
"msg/$id",
"import",
"imp/$id",
true );
736 $label = $this->msg(
'translate-manage-action-ignore-change' )->text();
737 $actions .= Xml::radioLabel( $label,
"msg/$id",
"ignore",
"i/$id" );
741 $addedContent = ContentHandler::makeContent( (
string)$addedMsg[
'content'], $addedTitle );
742 $deletedContent = ContentHandler::makeContent( (
string)$deletedMsg[
'content'], $deletedTitle );
743 $this->diff->setContent( $deletedContent, $addedContent );
748 $menu = Html::rawElement(
751 'class' =>
'smg-rename-actions',
753 'data-group-id' => $group->
getId(),
754 'data-msgkey' => $addedKey,
755 'data-msgtitle' => $addedTitle->getFullText()
760 $actions = Html::rawElement(
'div', [
'class' =>
'smg-change-import-options' ], $actions );
762 $text = $this->diff->getDiff(
764 $addedTitleLink . $menu . $actions,
765 $isEqual ? htmlspecialchars( $addedMsg[
'content'] ) :
''
768 $hidden = Html::hidden( $id, 1 );
772 return Html::rawElement(
774 [
'class' =>
'mw-translate-smg-change smg-change-rename' ],
779 private function getRenameJobParams(
781 MessageSourceChange $sourceChanges,
782 string $languageCode,
785 bool $isSourceLang =
true
787 if ( $selectedVal ===
'ignore' ) {
792 $replacementContent =
'';
793 $currentMsgKey = $currentMsg[
'key'];
794 $matchedMsg = $sourceChanges->getMatchedMessage( $languageCode, $currentMsgKey );
795 if ( $matchedMsg ===
null ) {
796 throw new RuntimeException(
"Could not find matched message for $currentMsgKey." );
798 $matchedMsgKey = $matchedMsg[
'key'];
801 $sourceChanges->isPreviousState(
804 [ MessageSourceChange::ADDITION, MessageSourceChange::CHANGE ]
807 $params[
'target'] = $matchedMsgKey;
808 $params[
'replacement'] = $currentMsgKey;
809 $replacementContent = $currentMsg[
'content'];
811 $params[
'target'] = $currentMsgKey;
812 $params[
'replacement'] = $matchedMsgKey;
813 $replacementContent = $matchedMsg[
'content'];
816 $params[
'fuzzy'] = $selectedVal ===
'renamefuzzy';
818 $params[
'content'] = $replacementContent;
820 if ( $isSourceLang ) {
821 $params[
'targetTitle'] = Title::newFromText(
822 Utilities::title( $params[
'target'], $languageCode, $groupNamespace ),
825 $params[
'others'] = [];
831 private function handleRenameSubmit(
833 MessageSourceChange $sourceChanges,
838 array &$modificationJobs
840 $groupId = $group->getId();
841 $renames = $sourceChanges->getRenames( $language );
845 foreach ( $renames as $key => $params ) {
848 if ( !isset( $renames[$key] ) ) {
852 $id = self::changeId( $groupId, $language, MessageSourceChange::RENAME, $key );
854 [ $renameMissing, $isCurrentKeyPresent ] = $this->isRenameMissing(
864 if ( $renameMissing ) {
867 $postponed[$groupId][$language][MessageSourceChange::RENAME][$key] = $params;
871 if ( !$isCurrentKeyPresent ) {
876 $selectedVal = $req->getVal(
"msg/$id" );
877 $jobParams = $this->getRenameJobParams(
886 if ( $jobParams ===
null ) {
890 $targetStr = $jobParams[
'target' ];
891 if ( $isSourceLang ) {
892 $jobData[ $targetStr ] = $jobParams;
893 } elseif ( isset( $jobData[ $targetStr ] ) ) {
897 $jobData[ $targetStr ][
'others' ][ $language ] = $jobParams[
'content' ];
901 $title = Title::newFromText(
902 Utilities::title( $targetStr, $language, $groupNamespace ),
909 $matchedKey = $sourceChanges->getMatchedKey( $language, $key );
910 unset( $renames[$matchedKey] );
914 private function handleModificationsSubmit(
916 MessageSourceChange $sourceChanges,
920 array &$messageUpdateJob
922 $groupId = $group->getId();
923 $subchanges = $sourceChanges->getModificationsForLanguage( $language );
926 unset( $subchanges[ MessageSourceChange::RENAME ] );
929 foreach ( $subchanges as $type => $messages ) {
930 foreach ( $messages as $index => $params ) {
931 $key = $params[
'key'];
932 $id = self::changeId( $groupId, $language, $type, $key );
933 $title = Title::makeTitleSafe( $group->
getNamespace(),
"$key/$language" );
935 if ( !$this->isTitlePresent( $title, $type ) ) {
939 if ( !$req->getCheck( $id ) ) {
941 $postponed[$groupId][$language][$type][$index] = $params;
945 $selectedVal = $req->getVal(
"msg/$id" );
946 if ( $type === MessageSourceChange::DELETION || $selectedVal ===
'ignore' ) {
950 $fuzzy = $selectedVal ===
'fuzzy';
957 private function createRenameJobs( array $jobParams ): array {
959 foreach ( $jobParams as $groupId => $groupJobParams ) {
960 $jobs[$groupId] = $jobs[$groupId] ?? [];
961 foreach ( $groupJobParams as $params ) {
963 $params[
'targetTitle'],
965 $params[
'replacement'],
977 private function isTitlePresent( Title $title,
string $type ): bool {
980 ( $type === MessageSourceChange::DELETION || $type === MessageSourceChange::CHANGE ) &&
1001 private function isRenameMissing(
1003 MessageSourceChange $sourceChanges,
1010 if ( $req->getCheck( $id ) ) {
1011 return [
false, true ];
1014 $isCurrentKeyPresent =
false;
1017 $matchedKey = $sourceChanges->getMatchedKey( $language, $key );
1018 $matchedId = self::changeId( $groupId, $language, MessageSourceChange::RENAME, $matchedKey );
1019 if ( $req->getCheck( $matchedId ) ) {
1020 return [
false, $isCurrentKeyPresent ];
1026 $isSourceLang || !$sourceChanges->isEqual( $language, $matchedKey ),
1027 $isCurrentKeyPresent
1031 private function getProcessingErrorMessage( array $errorGroups,
int $totalGroupCount ): string {
1033 if ( count( $errorGroups ) < $totalGroupCount ) {
1034 $errorMsg = $this->msg(
'translate-smg-submitted-with-failure' )
1035 ->numParams( count( $errorGroups ) )
1037 $this->getLanguage()->commaList( $errorGroups ),
1038 $this->msg(
'translate-smg-submitted-others-processing' )
1042 $this->msg(
'translate-smg-submitted-with-failure' )
1043 ->numParams( count( $errorGroups ) )
1044 ->params( $this->getLanguage()->commaList( $errorGroups ),
'' )
1053 private function getGroupsFromCdb( \Cdb\Reader $reader ): array {
1055 $groupIds = Utilities::deserialize( $reader->get(
'#keys' ) );
1056 foreach ( $groupIds as $id ) {
1057 $groups[$id] = MessageGroups::getGroup( $id );
1059 return array_filter( $groups );
1067 private function startSync( array $modificationJobs, array $renameJobs ): void {
1070 $modificationGroupIds = array_keys( array_filter( $modificationJobs ) );
1071 $renameGroupIds = array_keys( array_filter( $renameJobs ) );
1072 $uniqueGroupIds = array_unique( array_merge( $modificationGroupIds, $renameGroupIds ) );
1073 $jobQueueInstance = $this->jobQueueGroup;
1075 foreach ( $uniqueGroupIds as $groupId ) {
1080 $groupRenameJobs = $renameJobs[$groupId] ?? [];
1082 foreach ( $groupRenameJobs as $job ) {
1083 $groupJobs[] = $job;
1084 $messageUpdateParam = MessageUpdateParameter::createFromJob( $job );
1085 $messages[] = $messageUpdateParam;
1088 $replacement = $messageUpdateParam->getReplacementValue();
1089 $targetTitle = Title::makeTitle( $job->getTitle()->getNamespace(), $replacement );
1090 $messageKeys[] = (
new MessageHandle( $targetTitle ) )->getKey();
1093 $groupModificationJobs = $modificationJobs[$groupId] ?? [];
1095 foreach ( $groupModificationJobs as $job ) {
1096 $groupJobs[] = $job;
1097 $messageUpdateParam = MessageUpdateParameter::createFromJob( $job );
1098 $messages[] = $messageUpdateParam;
1100 $messageKeys[] = (
new MessageHandle( $job->getTitle() ) )->getKey();
1105 $group = MessageGroups::getGroup( $groupId );
1106 $this->messageIndex->storeInterim( $group, $messageKeys );
1108 if ( $this->getConfig()->
get(
'TranslateGroupSynchronizationCache' ) ) {
1109 $this->synchronizationCache->addMessages( $groupId, ...$messages );
1110 $this->synchronizationCache->markGroupForSync( $groupId );
1112 LoggerFactory::getInstance(
'Translate.GroupSynchronization' )->info(
1113 '[' . __CLASS__ .
'] Synchronization started for {groupId} by {user}',
1115 'groupId' => $groupId,
1116 'user' => $this->getUser()->getName()
1125 DeferredUpdates::addCallableUpdate(
1126 static function () use ( $jobQueueInstance, $groupJobs ) {
1127 $jobQueueInstance->push( $groupJobs );
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