51 private const GROUP_SYNC_INFO_WRAPPER_CLASS =
'smg-group-sync-cache-info';
52 private const RIGHT =
'translate-manage';
53 protected DifferenceEngine $diff;
58 private Language $contLang;
59 private NamespaceInfo $nsInfo;
60 private RevisionLookup $revLookup;
63 private JobQueueGroup $jobQueueGroup;
65 private LinkBatchFactory $linkBatchFactory;
67 public function __construct(
69 NamespaceInfo $nsInfo,
70 RevisionLookup $revLookup,
72 JobQueueGroup $jobQueueGroup,
74 LinkBatchFactory $linkBatchFactory
77 parent::__construct(
'ManageMessageGroups' );
78 $this->contLang = $contLang;
79 $this->nsInfo = $nsInfo;
80 $this->revLookup = $revLookup;
81 $this->synchronizationCache = $synchronizationCache;
83 $this->jobQueueGroup = $jobQueueGroup;
84 $this->messageIndex = $messageIndex;
85 $this->linkBatchFactory = $linkBatchFactory;
88 public function doesWrites() {
92 protected function getGroupName() {
96 public function getDescription() {
97 return $this->msg(
'managemessagegroups' );
100 public function execute( $par ) {
103 $out = $this->getOutput();
104 $out->addModuleStyles(
'ext.translate.specialpages.styles' );
105 $out->addModules(
'ext.translate.special.managegroups' );
106 $out->addHelpLink(
'Help:Extension:Translate/Group_management' );
108 $name = $par ?: MessageChangeStorage::DEFAULT_NAME;
112 if ( $this->getConfig()->
get(
'TranslateGroupSynchronizationCache' ) ) {
114 $this->displayGroupSyncInfo->getGroupsInSyncHtml(
115 $this->synchronizationCache->getGroupsInSync(),
116 self::GROUP_SYNC_INFO_WRAPPER_CLASS
121 $this->displayGroupSyncInfo->getHtmlForGroupsWithError(
122 $this->synchronizationCache,
123 self::GROUP_SYNC_INFO_WRAPPER_CLASS,
131 $out->addWikiMsg(
'translate-smg-nochanges' );
136 $user = $this->getUser();
137 $this->hasRight = $user->isAllowed( self::RIGHT );
139 $req = $this->getRequest();
140 if ( !$req->wasPosted() ) {
141 $this->showChanges( $this->
getLimit() );
146 $block = $user->getBlock();
147 if ( $block && $block->isSitewide() ) {
148 throw new UserBlockedError(
151 $this->getLanguage(),
156 $csrfTokenSet = $this->getContext()->getCsrfTokenSet();
157 if ( !$this->hasRight || !$csrfTokenSet->matchTokenField(
'token' ) ) {
158 throw new PermissionsError( self::RIGHT );
161 $this->processSubmit();
168 ini_get(
'max_input_vars' ),
169 ini_get(
'suhosin.post.max_vars' ),
170 ini_get(
'suhosin.request.max_vars' )
173 $limits = array_filter( $limits );
174 return (
int)min( $limits );
177 protected function getLegend(): string {
178 $text = $this->diff->addHeader(
180 $this->msg(
'translate-smg-left' )->escaped(),
181 $this->msg(
'translate-smg-right' )->escaped()
184 return Html::rawElement(
'div', [
'class' =>
'mw-translate-smg-header' ], $text );
187 protected function showChanges(
int $limit ): void {
188 $diff = new DifferenceEngine( $this->getContext() );
189 $diff->showDiffStyle();
190 $diff->setReducedLineNumbers();
193 $out = $this->getOutput();
195 Html::openElement(
'form', [
'method' =>
'post' ] ) .
196 Html::hidden(
'title', $this->getPageTitle()->getPrefixedText(), [
197 'id' =>
'smgPageTitle'
199 Html::hidden(
'token', $this->getContext()->getCsrfTokenSet()->getToken() ) .
200 Html::hidden(
'changesetModifiedTime',
201 MessageChangeStorage::getLastModifiedTime( $this->cdb ) ) .
208 $groupSyncCacheEnabled = $this->getConfig()->get(
'TranslateGroupSynchronizationCache' );
209 if ( $groupSyncCacheEnabled ) {
211 $this->displayGroupSyncInfo->getGroupsInSyncHtml(
212 $this->synchronizationCache->getGroupsInSync(),
213 self::GROUP_SYNC_INFO_WRAPPER_CLASS
218 $this->displayGroupSyncInfo->getHtmlForGroupsWithError(
219 $this->synchronizationCache,
220 self::GROUP_SYNC_INFO_WRAPPER_CLASS,
226 $reader = \Cdb\Reader::open( $this->cdb );
227 $groups = $this->getGroupsFromCdb( $reader );
228 foreach ( $groups as $id => $group ) {
229 $sourceChanges = MessageSourceChange::loadModifications(
230 Utilities::deserialize( $reader->get( $id ) )
232 $out->addHTML( Html::element(
'h2', [], $group->getLabel() ) );
234 if ( $groupSyncCacheEnabled && $this->synchronizationCache->groupHasErrors( $id ) ) {
236 Html::warningBox( $this->msg(
'translate-smg-group-sync-error-warn' )->escaped(),
'center' )
241 $lb = $this->linkBatchFactory->newLinkBatch();
242 $ns = $group->getNamespace();
243 $isCap = $this->nsInfo->isCapitalized( $ns );
244 $languages = $sourceChanges->getLanguages();
246 foreach ( $languages as $language ) {
247 $languageChanges = $sourceChanges->getModificationsForLanguage( $language );
248 foreach ( $languageChanges as $type => $changes ) {
249 foreach ( $changes as $params ) {
251 $key = $params[
'key'];
253 $key = $this->contLang->ucfirst( $key );
255 $lb->add( $ns,
"$key/$language" );
261 foreach ( $languages as $language ) {
264 $changes[ MessageSourceChange::ADDITION ] = $sourceChanges->getAdditions( $language );
265 $changes[ MessageSourceChange::DELETION ] = $sourceChanges->getDeletions( $language );
266 $changes[ MessageSourceChange::CHANGE ] = $sourceChanges->getChanges( $language );
268 foreach ( $changes as $type => $messages ) {
269 foreach ( $messages as $params ) {
270 $change = $this->formatChange( $group, $sourceChanges, $language, $type, $params, $limit );
271 $out->addHTML( $change );
276 $out->wrapWikiMsg(
"<div class=warning>\n$1\n</div>",
'translate-smg-more' );
283 $this->showRenames( $group, $sourceChanges, $out, $language, $limit );
288 $button =
new ButtonInputWidget( [
290 'label' => $this->msg(
'translate-smg-submit' )->plain(),
291 'disabled' => !$this->hasRight ?
'disabled' : null,
292 'classes' => [
'mw-translate-smg-submit' ],
293 'title' => !$this->hasRight ? $this->msg(
'translate-smg-notallowed' )->plain() : null,
294 'flags' => [
'primary',
'progressive' ],
296 $out->addHTML( $button );
297 $out->addHTML( Html::closeElement(
'form' ) );
300 protected function formatChange(
302 MessageSourceChange $changes,
308 $key = $params[
'key'];
309 $title = Title::makeTitleSafe( $group->
getNamespace(),
"$key/$language" );
310 $id = self::changeId( $group->
getId(), $language, $type, $key );
312 $isReusedKey =
false;
314 if ( $title && $type ===
'addition' && $title->exists() ) {
323 $noticeHtml .= Html::warningBox( $this->msg(
'translate-manage-key-reused' )->text() );
325 } elseif ( $title && ( $type ===
'deletion' || $type ===
'change' ) && !$title->exists() ) {
332 $titleLink = $this->getLinkRenderer()->makeLink( $title );
334 if ( $type ===
'deletion' ) {
335 $revTitle = $this->revLookup->getRevisionByTitle( $title );
337 wfWarn(
"[ManageGroupSpecialPage] No revision associated with {$title->getPrefixedText()}" );
339 $content = $revTitle ? $revTitle->getContent( SlotRecord::MAIN ) :
null;
340 $wiki = ( $content instanceof TextContent ) ? $content->getText() :
'';
342 if ( $wiki ===
'' ) {
343 $noticeHtml .= Html::warningBox(
344 $this->msg(
'translate-manage-empty-content' )->text()
348 $oldContent = ContentHandler::makeContent( (
string)$wiki, $title );
349 $newContent = ContentHandler::makeContent(
'', $title );
350 $this->diff->setContent( $oldContent, $newContent );
351 $text = $this->diff->getDiff( $titleLink,
'', $noticeHtml );
352 } elseif ( $type ===
'addition' ) {
355 if ( $sourceLanguage === $language ) {
356 if ( $this->hasRight ) {
357 $menu = Html::rawElement(
360 'class' =>
'smg-rename-actions',
362 'data-group-id' => $group->
getId(),
363 'data-lang' => $language,
364 'data-msgkey' => $key,
365 'data-msgtitle' => $title->getFullText()
370 } elseif ( !self::isMessageDefinitionPresent( $group, $changes, $key ) ) {
371 $noticeHtml .= Html::warningBox(
372 $this->msg(
'translate-manage-source-message-not-found' )->text(),
373 'mw-translate-smg-notice-important'
377 $menu = Html::hidden(
"msg/$id",
'ignore', [
'id' =>
"i/$id" ] );
381 if ( $params[
'content'] ===
'' ) {
382 $noticeHtml .= Html::warningBox(
383 $this->msg(
'translate-manage-empty-content' )->text()
387 $oldContent = ContentHandler::makeContent(
'', $title );
388 $newContent = ContentHandler::makeContent( (
string)$params[
'content'], $title );
389 $this->diff->setContent( $oldContent, $newContent );
390 $text = $this->diff->getDiff(
'', $titleLink . $menu, $noticeHtml );
391 } elseif ( $type ===
'change' ) {
392 $wiki = Utilities::getContentForTitle( $title,
true );
399 $shouldFuzzy = $sourceLanguage === $language && $wiki !== $params[
'content'];
401 if ( $sourceLanguage === $language ) {
402 $label = $this->msg(
'translate-manage-action-fuzzy' )->text();
403 $actions .= Xml::radioLabel( $label,
"msg/$id",
"fuzzy",
"f/$id", $shouldFuzzy );
407 $sourceLanguage !== $language &&
409 !self::isMessageDefinitionPresent( $group, $changes, $key )
411 $noticeHtml .= Html::warningBox(
412 $this->msg(
'translate-manage-source-message-not-found' )->text(),
413 'mw-translate-smg-notice-important'
417 $actions .= Html::hidden(
"msg/$id",
'ignore', [
'id' =>
"i/$id" ] );
420 $label = $this->msg(
'translate-manage-action-import' )->text();
421 $actions .= Xml::radioLabel( $label,
"msg/$id",
"import",
"imp/$id", !$shouldFuzzy );
423 $label = $this->msg(
'translate-manage-action-ignore' )->text();
424 $actions .= Xml::radioLabel( $label,
"msg/$id",
"ignore",
"i/$id" );
428 $oldContent = ContentHandler::makeContent( (
string)$wiki, $title );
429 $newContent = ContentHandler::makeContent( (
string)$params[
'content'], $title );
431 $this->diff->setContent( $oldContent, $newContent );
432 $text .= $this->diff->getDiff( $titleLink, $actions, $noticeHtml );
435 $hidden = Html::hidden( $id, 1 );
438 $classes =
"mw-translate-smg-change smg-change-$type";
445 return Html::rawElement(
'div', [
'class' => $classes ], $text );
448 protected function processSubmit(): void {
449 $req = $this->getRequest();
450 $out = $this->getOutput();
453 $modificationJobs = $renameJobData = [];
454 $lastModifiedTime = intval( $req->getVal(
'changesetModifiedTime' ) );
456 if ( !MessageChangeStorage::isModifiedSince( $this->cdb, $lastModifiedTime ) ) {
457 $out->addWikiMsg(
'translate-smg-changeset-modified' );
461 $reader = \Cdb\Reader::open( $this->cdb );
462 $groups = $this->getGroupsFromCdb( $reader );
463 $groupSyncCacheEnabled = $this->getConfig()->get(
'TranslateGroupSynchronizationCache' );
466 foreach ( $groups as $groupId => $group ) {
469 throw new RuntimeException(
"Expected $groupId to be FileBasedMessageGroup, got "
470 . get_class( $group )
474 $changes = Utilities::deserialize( $reader->get( $groupId ) );
475 if ( $groupSyncCacheEnabled && $this->synchronizationCache->groupHasErrors( $groupId ) ) {
476 $postponed[$groupId] = $changes;
480 $sourceChanges = MessageSourceChange::loadModifications( $changes );
481 $groupModificationJobs = [];
482 $groupRenameJobData = [];
483 $languages = $sourceChanges->getLanguages();
484 foreach ( $languages as $language ) {
486 $this->handleModificationsSubmit(
492 $groupModificationJobs
496 $this->handleRenameSubmit(
503 $groupModificationJobs
506 if ( !isset( $postponed[$groupId][$language] ) ) {
507 $group->getMessageGroupCache( $language )->create();
511 if ( $groupSyncCacheEnabled && !isset( $postponed[ $groupId ] ) ) {
512 $this->synchronizationCache->markGroupAsReviewed( $groupId );
515 $modificationJobs[$groupId] = $groupModificationJobs;
516 $renameJobData[$groupId] = $groupRenameJobData;
517 }
catch ( Exception $e ) {
519 "ManageGroupsSpecialPage: Error in processSubmit. Group: $groupId\n" .
523 $errorGroups[] = $group->
getLabel();
527 $renameJobs = $this->createRenameJobs( $renameJobData );
528 $this->startSync( $modificationJobs, $renameJobs );
531 rename( $this->cdb, $this->cdb .
'-' . wfTimestamp() );
533 if ( $errorGroups ) {
534 $errorMsg = $this->getProcessingErrorMessage( $errorGroups, count( $groups ) );
538 'mw-translate-smg-submitted'
543 if ( count( $postponed ) ) {
544 $postponedSourceChanges = [];
545 foreach ( $postponed as $groupId => $changes ) {
546 $postponedSourceChanges[$groupId] = MessageSourceChange::loadModifications( $changes );
548 MessageChangeStorage::writeChanges( $postponedSourceChanges, $this->cdb );
550 $this->showChanges( $this->getLimit() );
551 } elseif ( $errorGroups === [] ) {
552 $out->addWikiMsg(
'translate-smg-submitted' );
556 protected static function changeId(
562 return 'smg/' . substr( sha1(
"$groupId/$language/$type/$key" ), 0, 7 );
569 public static function tabify( Skin $skin, array &$tabs ): void {
570 $title = $skin->getTitle();
571 if ( !$title->isSpecialPage() ) {
574 $specialPageFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
575 [ $alias, ] = $specialPageFactory->resolveAlias( $title->getText() );
578 'ManageMessageGroups' =>
'namespaces',
579 'AggregateGroups' =>
'namespaces',
580 'SupportedLanguages' =>
'views',
581 'TranslationStats' =>
'views',
583 if ( !isset( $pagesInGroup[$alias] ) ) {
587 $tabs[
'namespaces'] = [];
588 foreach ( $pagesInGroup as $spName => $section ) {
589 $spClass = $specialPageFactory->getPage( $spName );
591 if ( $spClass ===
null || $spClass instanceof DisabledSpecialPage ) {
594 $spTitle = $spClass->getPageTitle();
596 $tabs[$section][strtolower( $spName )] = [
597 'text' => $spClass->getDescription(),
598 'href' => $spTitle->getLocalURL(),
599 'class' => $alias === $spName ?
'selected' :
'',
608 private static function isMessageDefinitionPresent(
610 MessageSourceChange $changes,
613 $sourceLanguage = $group->getSourceLanguage();
614 if ( $changes->findMessage( $sourceLanguage, $msgKey, [ MessageSourceChange::ADDITION ] ) ) {
619 $sourceHandle =
new MessageHandle( Title::makeTitle( $namespace, $msgKey ) );
620 return $sourceHandle->isValid();
623 private function showRenames(
625 MessageSourceChange $sourceChanges,
630 $changes = $sourceChanges->getRenames( $language );
631 foreach ( $changes as $key => $params ) {
634 if ( !isset( $changes[ $key ] ) ) {
639 $sourceChanges->isEqual( $language, $key ) ) {
647 $secondKey = $sourceChanges->getMatchedKey( $language, $key ) ??
'';
648 $secondMsg = $sourceChanges->getMatchedMessage( $language, $key );
649 if ( $secondMsg ===
null ) {
650 throw new RuntimeException(
"Could not find matched message for $key" );
654 $sourceChanges->isPreviousState(
657 [ MessageSourceChange::ADDITION, MessageSourceChange::CHANGE ]
660 $addedMsg = $firstMsg;
661 $deletedMsg = $secondMsg;
663 $addedMsg = $secondMsg;
664 $deletedMsg = $firstMsg;
667 $change = $this->formatRename(
672 $sourceChanges->isEqual( $language, $key ),
675 $out->addHTML( $change );
678 unset( $changes[$secondKey] );
683 $out->wrapWikiMsg(
"<div class=warning>\n$1\n</div>",
'translate-smg-more' );
689 private function formatRename(
697 $addedKey = $addedMsg[
'key'];
698 $deletedKey = $deletedMsg[
'key'];
701 $addedTitle = Title::makeTitleSafe( $group->
getNamespace(),
"$addedKey/$language" );
702 $deletedTitle = Title::makeTitleSafe( $group->
getNamespace(),
"$deletedKey/$language" );
703 $id = self::changeId( $group->
getId(), $language, MessageSourceChange::RENAME, $addedKey );
705 $addedTitleLink = $this->getLinkRenderer()->makeLink( $addedTitle );
706 $deletedTitleLink = $this->getLinkRenderer()->makeLink( $deletedTitle );
708 $renameSelected =
true;
711 $renameSelected =
false;
712 $label = $this->msg(
'translate-manage-action-rename-fuzzy' )->text();
713 $actions .= Xml::radioLabel( $label,
"msg/$id",
"renamefuzzy",
"rf/$id",
true );
716 $label = $this->msg(
'translate-manage-action-rename' )->text();
717 $actions .= Xml::radioLabel( $label,
"msg/$id",
"rename",
"imp/$id", $renameSelected );
719 $label = $this->msg(
'translate-manage-action-import' )->text();
720 $actions .= Xml::radioLabel( $label,
"msg/$id",
"import",
"imp/$id",
true );
725 $label = $this->msg(
'translate-manage-action-ignore-change' )->text();
726 $actions .= Xml::radioLabel( $label,
"msg/$id",
"ignore",
"i/$id" );
730 $addedContent = ContentHandler::makeContent( (
string)$addedMsg[
'content'], $addedTitle );
731 $deletedContent = ContentHandler::makeContent( (
string)$deletedMsg[
'content'], $deletedTitle );
732 $this->diff->setContent( $deletedContent, $addedContent );
737 $menu = Html::rawElement(
740 'class' =>
'smg-rename-actions',
742 'data-group-id' => $group->
getId(),
743 'data-msgkey' => $addedKey,
744 'data-msgtitle' => $addedTitle->getFullText()
749 $actions = Html::rawElement(
'div', [
'class' =>
'smg-change-import-options' ], $actions );
751 $text = $this->diff->getDiff(
753 $addedTitleLink . $menu . $actions,
754 $isEqual ? htmlspecialchars( $addedMsg[
'content'] ) :
''
757 $hidden = Html::hidden( $id, 1 );
761 return Html::rawElement(
763 [
'class' =>
'mw-translate-smg-change smg-change-rename' ],
768 private function getRenameJobParams(
770 MessageSourceChange $sourceChanges,
771 string $languageCode,
774 bool $isSourceLang =
true
776 if ( $selectedVal ===
'ignore' ) {
781 $replacementContent =
'';
782 $currentMsgKey = $currentMsg[
'key'];
783 $matchedMsg = $sourceChanges->getMatchedMessage( $languageCode, $currentMsgKey );
784 if ( $matchedMsg ===
null ) {
785 throw new RuntimeException(
"Could not find matched message for $currentMsgKey." );
787 $matchedMsgKey = $matchedMsg[
'key'];
790 $sourceChanges->isPreviousState(
793 [ MessageSourceChange::ADDITION, MessageSourceChange::CHANGE ]
796 $params[
'target'] = $matchedMsgKey;
797 $params[
'replacement'] = $currentMsgKey;
798 $replacementContent = $currentMsg[
'content'];
800 $params[
'target'] = $currentMsgKey;
801 $params[
'replacement'] = $matchedMsgKey;
802 $replacementContent = $matchedMsg[
'content'];
805 $params[
'fuzzy'] = $selectedVal ===
'renamefuzzy';
807 $params[
'content'] = $replacementContent;
809 if ( $isSourceLang ) {
810 $params[
'targetTitle'] = Title::newFromText(
811 Utilities::title( $params[
'target'], $languageCode, $groupNamespace ),
814 $params[
'others'] = [];
820 private function handleRenameSubmit(
822 MessageSourceChange $sourceChanges,
827 array &$modificationJobs
829 $groupId = $group->getId();
830 $renames = $sourceChanges->getRenames( $language );
834 foreach ( $renames as $key => $params ) {
837 if ( !isset( $renames[$key] ) ) {
841 $id = self::changeId( $groupId, $language, MessageSourceChange::RENAME, $key );
843 [ $renameMissing, $isCurrentKeyPresent ] = $this->isRenameMissing(
853 if ( $renameMissing ) {
856 $postponed[$groupId][$language][MessageSourceChange::RENAME][$key] = $params;
860 if ( !$isCurrentKeyPresent ) {
865 $selectedVal = $req->getVal(
"msg/$id" );
866 $jobParams = $this->getRenameJobParams(
875 if ( $jobParams ===
null ) {
879 $targetStr = $jobParams[
'target' ];
880 if ( $isSourceLang ) {
881 $jobData[ $targetStr ] = $jobParams;
882 } elseif ( isset( $jobData[ $targetStr ] ) ) {
886 $jobData[ $targetStr ][
'others' ][ $language ] = $jobParams[
'content' ];
890 $title = Title::newFromText(
891 Utilities::title( $targetStr, $language, $groupNamespace ),
894 $modificationJobs[] = UpdateMessageJob::newJob( $title, $jobParams[
'content'] );
898 $matchedKey = $sourceChanges->getMatchedKey( $language, $key );
899 unset( $renames[$matchedKey] );
903 private function handleModificationsSubmit(
905 MessageSourceChange $sourceChanges,
909 array &$messageUpdateJob
911 $groupId = $group->getId();
912 $subchanges = $sourceChanges->getModificationsForLanguage( $language );
915 unset( $subchanges[ MessageSourceChange::RENAME ] );
918 foreach ( $subchanges as $type => $messages ) {
919 foreach ( $messages as $index => $params ) {
920 $key = $params[
'key'];
921 $id = self::changeId( $groupId, $language, $type, $key );
922 $title = Title::makeTitleSafe( $group->
getNamespace(),
"$key/$language" );
924 if ( !$this->isTitlePresent( $title, $type ) ) {
928 if ( !$req->getCheck( $id ) ) {
930 $postponed[$groupId][$language][$type][$index] = $params;
934 $selectedVal = $req->getVal(
"msg/$id" );
935 if ( $type === MessageSourceChange::DELETION || $selectedVal ===
'ignore' ) {
939 $fuzzy = $selectedVal ===
'fuzzy';
940 $messageUpdateJob[] = UpdateMessageJob::newJob( $title, $params[
'content'], $fuzzy );
946 private function createRenameJobs( array $jobParams ): array {
948 foreach ( $jobParams as $groupId => $groupJobParams ) {
949 $jobs[$groupId] ??= [];
950 foreach ( $groupJobParams as $params ) {
951 $jobs[$groupId][] = UpdateMessageJob::newRenameJob(
952 $params[
'targetTitle'],
954 $params[
'replacement'],
966 private function isTitlePresent( Title $title,
string $type ): bool {
969 ( $type === MessageSourceChange::DELETION || $type === MessageSourceChange::CHANGE ) &&
990 private function isRenameMissing(
992 MessageSourceChange $sourceChanges,
999 if ( $req->getCheck( $id ) ) {
1000 return [
false, true ];
1003 $isCurrentKeyPresent =
false;
1006 $matchedKey = $sourceChanges->getMatchedKey( $language, $key );
1007 $matchedId = self::changeId( $groupId, $language, MessageSourceChange::RENAME, $matchedKey );
1008 if ( $req->getCheck( $matchedId ) ) {
1009 return [
false, $isCurrentKeyPresent ];
1015 $isSourceLang || !$sourceChanges->isEqual( $language, $matchedKey ),
1016 $isCurrentKeyPresent
1020 private function getProcessingErrorMessage( array $errorGroups,
int $totalGroupCount ): string {
1022 if ( count( $errorGroups ) < $totalGroupCount ) {
1023 $errorMsg = $this->msg(
'translate-smg-submitted-with-failure' )
1024 ->numParams( count( $errorGroups ) )
1026 $this->getLanguage()->commaList( $errorGroups ),
1027 $this->msg(
'translate-smg-submitted-others-processing' )
1031 $this->msg(
'translate-smg-submitted-with-failure' )
1032 ->numParams( count( $errorGroups ) )
1033 ->params( $this->getLanguage()->commaList( $errorGroups ),
'' )
1042 private function getGroupsFromCdb( \Cdb\Reader $reader ): array {
1044 $groupIds = Utilities::deserialize( $reader->get(
'#keys' ) );
1045 foreach ( $groupIds as $id ) {
1046 $groups[$id] = MessageGroups::getGroup( $id );
1048 return array_filter( $groups );
1056 private function startSync( array $modificationJobs, array $renameJobs ): void {
1059 $modificationGroupIds = array_keys( array_filter( $modificationJobs ) );
1060 $renameGroupIds = array_keys( array_filter( $renameJobs ) );
1061 $uniqueGroupIds = array_unique( array_merge( $modificationGroupIds, $renameGroupIds ) );
1062 $jobQueueInstance = $this->jobQueueGroup;
1064 foreach ( $uniqueGroupIds as $groupId ) {
1069 $groupRenameJobs = $renameJobs[$groupId] ?? [];
1071 foreach ( $groupRenameJobs as $job ) {
1072 $groupJobs[] = $job;
1073 $messageUpdateParam = MessageUpdateParameter::createFromJob( $job );
1074 $messages[] = $messageUpdateParam;
1077 $replacement = $messageUpdateParam->getReplacementValue();
1078 $targetTitle = Title::makeTitle( $job->getTitle()->getNamespace(), $replacement );
1079 $messageKeys[] = (
new MessageHandle( $targetTitle ) )->getKey();
1082 $groupModificationJobs = $modificationJobs[$groupId] ?? [];
1084 foreach ( $groupModificationJobs as $job ) {
1085 $groupJobs[] = $job;
1086 $messageUpdateParam = MessageUpdateParameter::createFromJob( $job );
1087 $messages[] = $messageUpdateParam;
1089 $messageKeys[] = (
new MessageHandle( $job->getTitle() ) )->getKey();
1094 $group = MessageGroups::getGroup( $groupId );
1095 $this->messageIndex->storeInterim( $group, $messageKeys );
1097 if ( $this->getConfig()->
get(
'TranslateGroupSynchronizationCache' ) ) {
1098 $this->synchronizationCache->addMessages( $groupId, ...$messages );
1099 $this->synchronizationCache->markGroupForSync( $groupId );
1101 LoggerFactory::getInstance(
'Translate.GroupSynchronization' )->info(
1102 '[' . __CLASS__ .
'] Synchronization started for {groupId} by {user}',
1104 'groupId' => $groupId,
1105 'user' => $this->getUser()->getName()
1114 DeferredUpdates::addCallableUpdate(
1115 static function () use ( $jobQueueInstance, $groupJobs ) {
1116 $jobQueueInstance->push( $groupJobs );
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager( $services->getTitleFactory());}, '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( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), 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: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->getDBLoadBalancer());}, '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->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( 'Translate.MessageGroupSubscription'), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):MessageGroupSubscriptionHookHandler { return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getDBLoadBalancerFactory());}, '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( 'Translate'), $services->getMainObjectStash(), $services->getDBLoadBalancerFactory(), $services->get( 'Translate:MessageGroupSubscription'), 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->getDBLoadBalancer(), $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->getDBLoadBalancer());}, '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->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(), $services->getNamespaceInfo(), $services->getTitleFactory());}, '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->getDBLoadBalancerFactory(), $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:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getDBLoadBalancer(), $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'));}, '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->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getDBLoadBalancerFactory(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getDBLoadBalancer(), $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 { $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