5namespace MediaWiki\Extension\Translate\PageTranslation;
9use MediaWiki\Category\Category;
10use MediaWiki\CommentStore\CommentStoreComment;
11use MediaWiki\Config\Config;
12use MediaWiki\Content\Content;
13use MediaWiki\Content\TextContent;
14use MediaWiki\Context\IContextSource;
15use MediaWiki\Context\RequestContext;
16use MediaWiki\Deferred\DeferredUpdates;
17use MediaWiki\Deferred\LinksUpdate\LinksUpdate;
18use MediaWiki\Exception\UserBlockedError;
30use MediaWiki\Html\Html;
31use MediaWiki\Language\Language;
32use MediaWiki\Language\LanguageCode;
33use MediaWiki\Language\LanguageNameUtils;
34use MediaWiki\Linker\LinkTarget;
35use MediaWiki\Logger\LoggerFactory;
36use MediaWiki\Logging\ManualLogEntry;
37use MediaWiki\MainConfigNames;
38use MediaWiki\MediaWikiServices;
39use MediaWiki\Output\OutputPage;
40use MediaWiki\Page\Article;
41use MediaWiki\Page\PageIdentity;
42use MediaWiki\Page\PageReference;
43use MediaWiki\Page\WikiPage;
44use MediaWiki\Parser\Parser;
45use MediaWiki\Parser\ParserOutput;
46use MediaWiki\Parser\ParserOutputFlags;
47use MediaWiki\Parser\PPFrame;
48use MediaWiki\ResourceLoader\Context;
49use MediaWiki\Revision\MutableRevisionRecord;
50use MediaWiki\Revision\RenderedRevision;
51use MediaWiki\Revision\RevisionRecord;
52use MediaWiki\Revision\SlotRecord;
53use MediaWiki\Skin\Skin;
54use MediaWiki\SpecialPage\SpecialPage;
55use MediaWiki\Status\Status;
56use MediaWiki\Storage\EditResult;
57use MediaWiki\StubObject\StubUserLang;
58use MediaWiki\Title\Title;
59use MediaWiki\User\User;
60use MediaWiki\User\UserIdentity;
62use Wikimedia\Rdbms\IDBAccessObject;
63use Wikimedia\ScopedCallback;
72 private const PAGEPROP_HAS_LANGUAGES_TAG =
'translate-has-languages-tag';
74 public static $allowTargetEdit =
false;
78 public static $renderingContext =
false;
80 private static $languageLinkData = [];
89 public static function renderTagPage( $wikitextParser, &$text, $state ): void {
90 if ( $text === null ) {
98 if ( $wikitextParser->getOptions()->getInterfaceMessage() ) {
104 if ( $wikitextParser->getOptions()->getIsSectionPreview() ) {
105 $translatablePageParser = Services::getInstance()->getTranslatablePageParser();
106 $text = $translatablePageParser->cleanupTags( $text );
110 $title = MediaWikiServices::getInstance()
112 ->castFromPageReference( $wikitextParser->getPage() );
122 $wikitextParser->getOutput()->setUnsortedPageProperty(
'translate-is-translation' );
125 self::$renderingContext =
true;
127 $name = $page->getPageDisplayTitle( $code );
129 $name = $wikitextParser->recursivePreprocess( $name );
131 $langConv = MediaWikiServices::getInstance()->getLanguageConverterFactory()
132 ->getLanguageConverter( $wikitextParser->getTargetLanguage() );
133 $name = $langConv->convert( $name );
134 $wikitextParser->getOutput()->setDisplayTitle( $name );
136 self::$renderingContext =
false;
137 }
catch ( Exception $e ) {
139 'T302754 Failed to set display title for page {title}',
141 'title' => $title->getPrefixedDBkey(),
143 'pageid' => $title->getId(),
152 'languagecode' => $code,
153 'messagegroupid' => $page->getMessageGroupId(),
154 'sourcepagetitle' => [
155 'namespace' => $page->getTitle()->getNamespace(),
156 'dbkey' => $page->getTitle()->getDBkey()
160 $wikitextParser->getOutput()->setExtensionData(
'translate-translation-page', $extensionData );
162 $wikitextParser->getOutput()->setOutputFlag( ParserOutputFlags::NO_SECTION_EDIT_LINKS );
173 $translatablePageParser =
Services::getInstance()->getTranslatablePageParser();
175 if ( $translatablePageParser->containsMarkup( $text ) ) {
177 $parserOutput = $translatablePageParser->parse( $text );
179 $text = $parserOutput->sourcePageTextForRendering(
180 $wikitextParser->getTargetLanguage()
183 wfDebug(
'ParsingFailure caught; expected' );
189 $text = $unit->getTextForTrans();
205 ?LinkTarget $contextLink,
206 ?LinkTarget $templateLink,
208 ?RevisionRecord &$revRecord
210 if ( !$templateLink ) {
214 $templateTitle = Title::newFromLinkTarget( $templateLink );
216 $templateTranslationPage = TranslatablePage::isTranslationPage( $templateTitle );
217 if ( $templateTranslationPage ) {
220 $revRecord = $templateTranslationPage->getRevisionRecordWithFallback();
223 LoggerFactory::getInstance( LogNames::MAIN )->warning(
224 "T323863: Could not fetch any revision record for '{groupid}'",
225 [
'groupid' => $templateTranslationPage->getMessageGroupId() ]
231 if ( !TranslatablePage::isSourcePage( $templateTitle ) ) {
235 $translatableTemplatePage = TranslatablePage::newFromTitle( $templateTitle );
237 if ( !( $translatableTemplatePage->supportsTransclusion() ??
false ) ) {
242 $store = MediaWikiServices::getInstance()->getRevisionStore();
244 if ( $contextLink ) {
246 $templateTranslationTitle = $templateTitle->getSubpage(
247 Title::newFromLinkTarget( $contextLink )->getPageLanguage()->getCode()
250 if ( $templateTranslationTitle ) {
251 if ( $templateTranslationTitle->exists() ) {
253 $revRecord = $store->getRevisionByTitle( $templateTranslationTitle );
258 $revRecord =
new MutableRevisionRecord( $templateTranslationTitle );
266 $sourceTemplateTitle = $templateTitle->getSubpage(
267 $translatableTemplatePage->getMessageGroup()->getSourceLanguage()
269 if ( $sourceTemplateTitle && $sourceTemplateTitle->exists() ) {
270 $revRecord = $store->getRevisionByTitle( $sourceTemplateTitle );
283 if ( TranslatablePage::isTranslationPage( $title ) ) {
284 [ , $code ] = Utilities::figureMessage( $title->getText() );
285 $pageLang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $code );
297 if ( TranslatablePage::isSourcePage( $title ) ) {
298 $msg = wfMessage(
'translate-edit-tag-warning' )->inContentLanguage();
299 if ( !$msg->isDisabled() ) {
300 $notices[
'translate-tag'] = $msg->parseAsBlock();
303 $notices[] = Html::warningBox(
304 wfMessage(
'tps-edit-sourcepage-text' )->parse(),
305 'translate-edit-documentation'
310 $request = RequestContext::getMain()->getRequest();
311 if ( $request->getVal(
'action' ) ===
'visualeditor' &&
312 $request->getVal(
'paction' ) !==
'wikitext'
314 $notices[] = Html::warningBox(
315 wfMessage(
'tps-edit-sourcepage-ve-warning-limited-text' )->parse(),
316 'translate-edit-documentation'
328 $title = $out->getTitle();
329 if ( $title->isSpecialPage() ) {
333 $isSource = TranslatablePage::isSourcePage( $title );
334 $isTranslation = TranslatablePage::isTranslationPage( $title );
336 if ( $isSource || $isTranslation ) {
337 $out->addModules(
'ext.translate.pagetranslation.uls' );
341 $out->addModuleStyles(
'ext.translate.edit.documentation.styles' );
344 $out->addModuleStyles(
'ext.translate' );
346 $out->addJsConfigVars(
'wgTranslatePageTranslation', $isTranslation ?
'translation' :
'source' );
348 $groupId = TranslatablePage::getMessageGroupIdFromTitle( $title );
349 $out->addJsConfigVars(
'wgTranslatePageTranslationGroup', $groupId );
351 $priorityLanguages = Services::getInstance()->getMessageGroupMetadata()->get( $groupId,
'prioritylangs' );
352 if ( $priorityLanguages ) {
353 $priorityLanguages = explode(
',', $priorityLanguages );
355 $priorityLanguages = [];
357 $out->addJsConfigVars(
'wgTranslatePriorityLanguages', $priorityLanguages );
359 $viewTranslatablePage = Services::getInstance()->getTranslatablePageView();
360 $user = $out->getUser();
361 if ( $viewTranslatablePage->canDisplayTranslationSettingsBanner( $title, $user ) ) {
362 $out->addModules(
'ext.translate.uls.translation.banner' );
374 return !TranslatablePage::isTranslationPage( $out->getTitle() );
390 TextContent $content,
397 if ( $user->equals( FuzzyBot::getUser() ) ) {
407 $page = TranslatablePage::newFromTitle( $group->getTitle() );
410 if ( !$handle->
isDoc() ) {
412 DeferredUpdates::addCallableUpdate(
413 function () use ( $page, $code, $user, $flags, $summary, $handle ) {
415 self::updateTranslationPage( $page, $code, $user, $flags, $summary,
null, $unitTitle );
421 private static function updateTranslationPage(
427 ?
string $triggerAction =
null,
428 ?Title $unitTitle =
null
430 $source = $page->getTitle();
431 $target = $source->getSubpage( $code );
432 $mwInstance = MediaWikiServices::getInstance();
435 $flags &= ~EDIT_NEW & ~EDIT_UPDATE;
438 $unitTitleText = $unitTitle ? $unitTitle->getPrefixedText() :
null;
439 $job = RenderTranslationPageJob::newJob( $target, $triggerAction, $unitTitleText );
441 if ( !$user->equals( FuzzyBot::getUser() ) ) {
442 $session = RequestContext::getMain()->exportSession();
444 $job->setUser( $user, $session );
445 $job->setSummary( $summary );
446 $job->setFlags( $flags );
447 $mwInstance->getJobQueueGroup()->push( $job );
451 $wikiPageFactory = $mwInstance->getWikiPageFactory();
452 foreach ( $pages as $title ) {
453 if ( $title->equals( $target ) ) {
458 $wikiPage = $wikiPageFactory->newFromTitle( $title );
459 $wikiPage->doPurge();
461 $sourceWikiPage = $wikiPageFactory->newFromTitle( $source );
462 $sourceWikiPage->doPurge();
470 $variableIDs[] =
'translatablepage';
478 array &$variableCache,
483 switch ( $magicWordId ) {
484 case 'translatablepage':
485 $pageStatus = self::getTranslatablePageStatus( $parser->getPage() );
486 $ret = $pageStatus !==
null ? $pageStatus[
'page']->getTitle()->getPrefixedText() :
'';
487 $variableCache[$magicWordId] = $ret;
498 public static function languages( $data, $params, $parser ) {
499 global $wgPageTranslationLanguageList;
501 if ( $wgPageTranslationLanguageList ===
'sidebar-only' ) {
505 self::$renderingContext =
true;
506 $context =
new ScopedCallback(
static function () {
507 self::$renderingContext =
false;
512 $parser->getOutput()->setUnsortedPageProperty( self::PAGEPROP_HAS_LANGUAGES_TAG );
514 $currentPage = $parser->getPage();
515 $pageStatus = self::getTranslatablePageStatus( $currentPage );
516 if ( !$pageStatus ) {
520 $page = $pageStatus[
'page' ];
521 $status = $pageStatus[
'languages' ];
528 $userLang = $parser->getOptions()->getUserLangObj();
529 $userLangCode = $userLang->getCode();
533 $sourceLanguage = $pageTitle->getPageLanguage()->getCode();
536 $langFactory = MediaWikiServices::getInstance()->getLanguageFactory();
537 foreach ( $status as $code => $percent ) {
539 $name = Utilities::getLanguageName( $code, LanguageNameUtils::AUTONYMS );
542 $suffix = ( $code === $sourceLanguage ) ?
'' :
"/$code";
543 $targetTitleString = $pageTitle->getDBkey() . $suffix;
544 $subpage = Title::makeTitle( $pageTitle->getNamespace(), $targetTitleString );
547 if ( $code === $userLangCode ) {
548 $classes[] =
'mw-pt-languages-ui';
551 $linker = $parser->getLinkRenderer();
552 $lang = $langFactory->getLanguage( $code );
553 if ( $currentPage->isSamePageAs( $subpage ) ) {
554 $classes[] =
'mw-pt-languages-selected';
555 $classes = array_merge( $classes, self::tpProgressIcon( (
float)$percent ) );
558 'lang' => $lang->getHtmlCode(),
559 'dir' => $lang->getDir(),
562 $contents = Html::element(
'span', $attribs, $name );
563 } elseif ( $subpage->isKnown() ) {
565 if ( !is_string( $pagename ) ) {
566 $pagename = $subpage->getPrefixedText();
569 $classes = array_merge( $classes, self::tpProgressIcon( (
float)$percent ) );
571 $title = wfMessage(
'tpt-languages-nonzero' )
572 ->page( $parser->getPage() )
573 ->inLanguage( $userLang )
574 ->params( $pagename )
575 ->numParams( 100 * $percent )
580 'lang' => $lang->getHtmlCode(),
581 'dir' => $lang->getDir(),
584 $contents = $linker->makeKnownLink( $subpage, $name, $attribs );
589 $specialTranslateTitle = SpecialPage::getTitleFor(
'Translate' );
599 'title' => wfMessage(
'tpt-languages-zero' )
600 ->page( $parser->getPage() )
601 ->inLanguage( $userLang )
604 'lang' => $lang->getHtmlCode(),
605 'dir' => $lang->getDir(),
607 $contents = $linker->makeKnownLink( $specialTranslateTitle, $name, $attribs, $params );
609 $languages[ $name ] = Html::rawElement(
'li', [], $contents );
613 $collator = Collator::create(
'root' );
616 $collator->compare( ... )
618 $languages = array_values( $languages );
619 $languages = implode(
"\n", $languages );
621 $out = Html::openElement(
'div', [
622 'class' =>
'mw-pt-languages noprint navigation-not-searchable',
623 'lang' => $userLang->getHtmlCode(),
624 'dir' => $userLang->getDir()
626 $out .= Html::rawElement(
'div', [
'class' =>
'mw-pt-languages-label' ],
627 wfMessage(
'tpt-languages-legend' )
628 ->page( $parser->getPage() )
629 ->inLanguage( $userLang )
632 $out .= Html::rawElement(
634 [
'class' =>
'mw-pt-languages-list' ],
637 $out .= Html::closeElement(
'div' );
639 $parser->getOutput()->addModuleStyles( [
640 'ext.translate.tag.languages',
652 private static function tpProgressIcon(
float $percent ) {
653 $classes = [
'mw-pt-progress' ];
655 if ( $percent < 15 ) {
656 $classes[] =
'mw-pt-progress--low';
657 } elseif ( $percent < 70 ) {
658 $classes[] =
'mw-pt-progress--med';
659 } elseif ( $percent < 100 ) {
660 $classes[] =
'mw-pt-progress--high';
662 $classes[] =
'mw-pt-progress--complete';
671 private static function getTranslatablePageStatus( ?PageReference $pageReference ): ?array {
672 if ( $pageReference === null ) {
675 $title = Title::newFromPageReference( $pageReference );
677 $page = TranslatablePage::newFromTitle( $title );
679 $page = TranslatablePage::isTranslationPage( $title );
682 if ( $page ===
false || $page->
getMarkedTag() ===
null ) {
686 $status = $page->getTranslationPercentages();
691 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
693 $priorityLanguages = $messageGroupMetadata->get( $page->
getMessageGroupId(),
'prioritylangs' );
694 if ( $priorityLanguages !==
false && $priorityLanguages !==
'' ) {
695 $status += array_fill_keys( explode(
',', $priorityLanguages ), 0 );
700 'languages' => $status
710 global $wgPageTranslationLanguageList;
712 if ( $wgPageTranslationLanguageList ===
'tag-only' ) {
716 if ( $wgPageTranslationLanguageList ===
'sidebar-fallback' ) {
717 $pageProps = MediaWikiServices::getInstance()->getPageProps();
718 $languageProp = $pageProps->getProperties( $title, self::PAGEPROP_HAS_LANGUAGES_TAG );
719 if ( $languageProp !== [] ) {
726 $status = self::getTranslatablePageStatus( $title );
731 self::$renderingContext =
true;
732 $context =
new ScopedCallback(
static function () {
733 self::$renderingContext =
false;
736 $page = $status[
'page' ];
737 $languages = $status[
'languages' ];
738 $mwServices = MediaWikiServices::getInstance();
739 $en = $mwServices->getLanguageFactory()->getLanguage(
'en' );
742 $lb = $mwServices->getLinkBatchFactory()->newLinkBatch();
743 foreach ( array_keys( $languages ) as $code ) {
744 $title = $page->getTitle()->getSubpage( $code );
745 $lb->addObj( $title );
748 $languageNameUtils = $mwServices->getLanguageNameUtils();
749 foreach ( $languages as $code => $percentage ) {
750 $title = $page->getTitle()->getSubpage( $code );
751 $placeholderValue =
"$code-x-pagetranslation:{$title->getPrefixedText()}";
752 $translatedName = $page->getPageDisplayTitle( $code ) ?: $title->getPrefixedText();
754 if ( $title->exists() ) {
755 $href = $title->getLocalURL();
756 $classes = self::tpProgressIcon( (
float)$percentage );
757 $titleAttribute = wfMessage(
'tpt-languages-nonzero' )
758 ->params( $translatedName )
759 ->numParams( 100 * $percentage );
761 $href = SpecialPage::getTitleFor(
'Translate' )->getLocalURL( [
762 'group' => $page->getMessageGroupId(),
765 $classes = [
'mw-pt-progress--none' ];
766 $titleAttribute = wfMessage(
'tpt-languages-zero' );
769 self::$languageLinkData[ $placeholderValue ] = [
772 'classes' => $classes,
773 'autonym' => $en->ucfirst( $languageNameUtils->getLanguageName( $code ) ),
774 'title' => $titleAttribute,
778 $languageLinks[] = $placeholderValue;
795 $data = self::$languageLinkData[$link[
'text']] ??
null;
800 $link[
'class'] .=
' interwiki-pagetranslation ' . implode(
' ', $data[
'classes'] );
801 $link[
'href'] = $data[
'href'];
802 $link[
'text'] = $data[
'autonym'];
803 $link[
'title'] = $data[
'title']->inLanguage( $out->getLanguage()->getCode() )->text();
804 $link[
'lang'] = LanguageCode::bcp47( $data[
'language'] );
805 $link[
'hreflang'] = LanguageCode::bcp47( $data[
'language'] );
807 $out->addModuleStyles(
'ext.translate.tag.languages' );
825 $syntaxErrorStatus = self::tpSyntaxError( $context->getTitle(), $content );
827 if ( $syntaxErrorStatus ) {
828 $status->merge( $syntaxErrorStatus );
829 return $syntaxErrorStatus->isGood();
835 private static function tpSyntaxError( ?PageIdentity $page, ?Content $content ): ?Status {
836 if ( !$page || !self::isAllowedContentModel( $content, $page ) ) {
840 '@phan-var TextContent $content';
841 $text = $content->getText();
844 $text = TextContent::normalizeLineEndings( $text );
845 $status = Status::newGood();
846 $parser = Services::getInstance()->getTranslatablePageParser();
847 if ( $parser->containsMarkup( $text ) ) {
849 $parser->parse( $text );
850 }
catch ( ParsingFailure $e ) {
851 $status->fatal( $e->getMessageSpecification() );
870 RenderedRevision $renderedRevision,
872 CommentStoreComment $summary,
876 $content = $renderedRevision->getRevision()->getContent( SlotRecord::MAIN );
878 $status = self::tpSyntaxError(
879 $renderedRevision->getRevision()->getPage(),
884 $hookStatus->merge( $status );
885 return $status->isGood();
903 UserIdentity $userIdentity,
906 RevisionRecord $revisionRecord,
907 EditResult $editResult
909 $content = $wikiPage->getContent();
912 if ( !self::isAllowedContentModel( $content, $wikiPage ) ) {
916 '@phan-var TextContent $content';
917 $text = $content->getText();
919 $parser = Services::getInstance()->getTranslatablePageParser();
920 if ( $parser->containsMarkup( $text ) ) {
922 $page = TranslatablePage::newFromTitle( $wikiPage->getTitle() );
927 $tpStatusUpdater = Services::getInstance()->getTranslatablePageStore();
928 $tpStatusUpdater->performStatusUpdate( $wikiPage->getTitle() );
946 $parentId = $rev->getParentId();
947 if ( $parentId === 0 || $parentId ===
null ) {
952 $prevRev = MediaWikiServices::getInstance()
953 ->getRevisionLookup()
954 ->getRevisionById( $parentId );
956 if ( !$prevRev || !$rev->hasSameContent( $prevRev ) ) {
961 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
962 $bundle = $bundleFactory->getBundle( $rev->getPage() );
965 $bundleStore = $bundleFactory->getStore( $bundle );
966 $bundleStore->handleNullRevisionInsert( $bundle, $rev );
992 if ( !$handle->isPageTranslation() || $action ===
'read' ) {
999 if ( $handle->isValid() ) {
1000 $group = $handle->getGroup();
1001 $groupId = $group->getId();
1002 $permissionTitleCheck =
null;
1005 $permissionTitleCheck = $group->getTitle();
1009 $permissionTitleCheck = Title::newFromID( $group->getBundlePageId() );
1012 if ( $permissionTitleCheck ) {
1013 if ( $handle->getCode() === $group->getSourceLanguage() && !$user->equals( FuzzyBot::getUser() ) ) {
1018 $allowedActionList = [
1019 'read',
'deletedtext',
'deletedhistory',
1020 'deleterevision',
'suppressrevision',
'viewsuppressed',
1024 if ( !in_array( $action, $allowedActionList ) ) {
1025 $result = [
'tpt-cant-edit-source-language', $permissionTitleCheck ];
1030 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1031 if ( $permissionManager->isBlockedFrom( $user, $permissionTitleCheck ) ) {
1032 $block = $user->getBlock();
1034 $error =
new UserBlockedError( $block, $user );
1035 $errorMessage = $error->getMessageObject();
1036 $result = array_merge( [ $errorMessage->getKey() ], $errorMessage->getParams() );
1040 if ( $action ===
'create' && $permissionTitleCheck->inNamespace( NS_CATEGORY ) ) {
1041 $renderedPage = $permissionTitleCheck->getSubpage( $handle->getCode() );
1042 if ( !$renderedPage->exists() ) {
1043 $cat = Category::newFromTitle( $renderedPage );
1044 if ( $cat->getMemberCount() === 0 ) {
1045 if ( !$permissionManager->userCan(
'translate-empty-category', $user, $renderedPage ) ) {
1046 $result = [
'tpt-create-empty-category', $renderedPage ];
1057 if ( $action !==
'create' ) {
1061 if ( !$handle->isValid() ) {
1067 $translatablePage = self::checkTranslatablePageSlow( $title );
1068 MediaWikiServices::getInstance()->getStatsFactory()
1069 ->withComponent(
'Translate' )
1070 ->getCounter(
'slow_translatable_page_check' )
1071 ->setLabel(
'valid', $translatablePage ?
'yes' :
'no' )
1074 if ( $translatablePage ) {
1075 $groupId = $translatablePage->getMessageGroupId();
1082 $error = self::getTranslationRestrictions( $handle, $groupId );
1083 $result = $error ?: $result;
1084 return $error === [];
1088 LoggerFactory::getInstance( LogNames::MAIN )->info(
1089 'Unknown translation page: {title}',
1090 [
'title' => $title->getPrefixedDBkey() ]
1092 $result = [
'tpt-unknown-page' ];
1096 private static function checkTranslatablePageSlow( LinkTarget $unit ): ?
TranslatablePage {
1098 $translationPageTitle = Title::newFromText(
1099 $parts[
'sourcepage' ] .
'/' . $parts[
'language' ]
1101 if ( !$translationPageTitle ) {
1105 $translatablePage = TranslatablePage::isTranslationPage( $translationPageTitle );
1106 if ( !$translatablePage ) {
1110 $factory = Services::getInstance()->getTranslationUnitStoreFactory();
1111 $store = $factory->getReader( $translatablePage->getPageIdentity() );
1112 $units = $store->getNames();
1114 if ( !in_array( $parts[
'section' ], $units ) ) {
1118 return $translatablePage;
1128 private static function getTranslationRestrictions( MessageHandle $handle, MessageGroup|
string $group ) {
1129 global $wgTranslateDocumentationLanguageCode;
1132 if ( $handle->getCode() === $wgTranslateDocumentationLanguageCode ) {
1136 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
1138 $force = $messageGroupMetadata->get( $group,
'priorityforce' );
1139 if ( $force !==
'on' ) {
1144 $languages = $messageGroupMetadata->get( $group,
'prioritylangs' );
1145 $reason = $messageGroupMetadata->get( $group,
'priorityreason' );
1146 if ( !$languages ) {
1148 return [
'tpt-translation-restricted-no-priority-languages', $reason ];
1150 return [
'tpt-translation-restricted-no-priority-languages-no-reason' ];
1153 $filter = array_flip( explode(
',', $languages ) );
1154 if ( !isset( $filter[$handle->getCode()] ) ) {
1156 return [
'tpt-translation-restricted', $reason ];
1159 return [
'tpt-translation-restricted-no-reason' ];
1175 if ( self::$allowTargetEdit ) {
1180 'read',
'deletedtext',
'deletedhistory',
1181 'deleterevision',
'suppressrevision',
'viewsuppressed',
1184 'translate-empty-category'
1186 $needsPageTranslationRight = in_array( $action, [
'delete',
'undelete' ] );
1187 if ( in_array( $action, $inclusionList ) ||
1188 ( $needsPageTranslationRight && $user->isAllowed(
'pagetranslation' ) )
1193 $page = TranslatablePage::isTranslationPage( $title );
1195 $mwService = MediaWikiServices::getInstance();
1196 if ( $needsPageTranslationRight ) {
1197 $context = RequestContext::getMain();
1198 $statusFormatter = $mwService->getFormatterFactory()->getStatusFormatter( $context );
1199 $permissionError = $mwService->getPermissionManager()
1200 ->newFatalPermissionDeniedStatus(
'pagetranslation', $context );
1201 $result = $statusFormatter->getMessage( $permissionError );
1205 [ , $code ] = Utilities::figureMessage( $title->getText() );
1209 'tpt-source-mirror',
1210 ':' . $page->
getTitle()->getPrefixedText()
1215 $translationUrl = $mwService->getUrlUtils()->expand(
1221 ':' . $page->
getTitle()->getPrefixedText(),
1237 public static function preventMoves( Title $oldTitle, Title $newTitle, StatusValue $status ) {
1238 if ( self::$allowTargetEdit ) {
1242 if ( TranslatablePage::newFromTitle( $oldTitle )->getMarkedTag() !==
null ) {
1243 $status->fatal(
'tpt-manual-move-source', $oldTitle->getPrefixedText() );
1247 if ( $oldTitle->getContentModel() === MessageBundleContent::CONTENT_MODEL_ID ) {
1248 $status->fatal(
'mb-manual-move', $oldTitle->getPrefixedText() );
1251 $tp = TranslatablePage::isTranslationPage( $oldTitle );
1254 $status->fatal(
'tpt-manual-move-translation', $tp->getTitle()->getPrefixedText() );
1269 $page = $article->getPage();
1271 $bundle = Services::getInstance()->getTranslatableBundleFactory()->getBundle( $page );
1272 $isDeletableBundle = $bundle && $bundle->isDeletable();
1273 if ( $isDeletableBundle || TranslatablePage::isTranslationPage( $title ) ) {
1274 $new = SpecialPage::getTitleFor(
1275 'PageTranslationDeletePage',
1276 $title->getPrefixedText()
1278 $out->redirect( $new->getFullURL() );
1292 if ( $article->getOldID() ) {
1296 $articleTitle = $article->getTitle();
1297 $transPage = TranslatablePage::isTranslationPage( $articleTitle );
1298 $context = $article->getContext();
1300 self::translationPageHeader( $context, $transPage );
1302 $viewTranslatablePage = Services::getInstance()->getTranslatablePageView();
1303 $user = $context->getUser();
1304 if ( $viewTranslatablePage->canDisplayTranslationSettingsBanner( $articleTitle, $user ) ) {
1305 $output = $context->getOutput();
1306 $pageUrl = SpecialPage::getTitleFor(
'PageTranslation' )->getFullURL( [
1308 'target' => $articleTitle->getPrefixedDBkey(),
1312 $context->msg(
'pt-cta-mark-translation', $pageUrl )->parse(),
1313 'translate-cta-pt-mark'
1317 self::sourcePageHeader( $context );
1322 private static function sourcePageHeader( IContextSource $context ) {
1323 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1325 $language = $context->getLanguage();
1326 $title = $context->getTitle();
1328 $page = TranslatablePage::newFromTitle( $title );
1332 $latest = $title->getLatestRevID();
1335 if ( $marked && $context->getUser()->isAllowed(
'translate' ) ) {
1336 $actions[] = self::getTranslateLink( $context, $page,
null );
1339 $hasChanges = $ready === $latest && $marked !== $latest;
1340 if ( $hasChanges ) {
1341 $diffUrl = $title->getFullURL( [
'oldid' => $marked,
'diff' => $latest ] );
1343 if ( $context->getUser()->isAllowed(
'pagetranslation' ) ) {
1344 $pageTranslation = SpecialPage::getTitleFor(
'PageTranslation' );
1345 $params = [
'target' => $title->getPrefixedText(),
'do' =>
'mark' ];
1347 if ( $marked ===
null ) {
1349 $linkDesc = $context->msg(
'translate-tag-markthis' )->text();
1350 $actions[] = $linker->makeKnownLink( $pageTranslation, $linkDesc, [], $params );
1352 $markUrl = $pageTranslation->getFullURL( $params );
1353 $actions[] = $context->msg(
'translate-tag-markthisagain', $diffUrl, $markUrl )
1357 $actions[] = $context->msg(
'translate-tag-hasnew', $diffUrl )->parse();
1361 if ( !count( $actions ) ) {
1365 $header = Html::rawElement(
1368 'class' =>
'mw-pt-translate-header noprint nomobile',
1369 'dir' => $language->getDir(),
1370 'lang' => $language->getHtmlCode(),
1372 $language->semicolonList( $actions )
1375 $context->getOutput()->addHTML( $header );
1378 private static function getTranslateLink(
1379 IContextSource $context,
1380 TranslatablePage $page,
1383 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1385 return $linker->makeKnownLink(
1386 SpecialPage::getTitleFor(
'Translate' ),
1387 $context->msg(
'translate-tag-translate-link-desc' )->text(),
1390 'group' => $page->getMessageGroupId(),
1391 'language' => $langCode,
1394 'action_source' =>
'translate_page'
1399 private static function translationPageHeader( IContextSource $context, TranslatablePage $page ) {
1400 global $wgTranslateKeepOutdatedTranslations;
1402 $title = $context->getTitle();
1403 if ( !$title->exists() ) {
1407 [ , $code ] = Utilities::figureMessage( $title->getText() );
1410 $pers = $page->getTranslationPercentages();
1412 if ( isset( $pers[$code] ) ) {
1413 $per = $pers[$code] * 100;
1416 $language = $context->getLanguage();
1417 $output = $context->getOutput();
1419 if ( $page->getSourceLanguageCode() === $code ) {
1421 $msg = self::getTranslateLink( $context, $page, $language->getCode() );
1423 $mwService = MediaWikiServices::getInstance();
1425 $translationUrl = $mwService->getUrlUtils()->expand(
1426 $page->getTranslationUrl( $code ), PROTO_RELATIVE
1429 $msg = $context->msg(
'tpt-translation-intro',
1431 ':' . $page->getTitle()->getPrefixedText(),
1432 $language->formatNum( $per )
1436 $header = Html::rawElement(
1439 'class' =>
'mw-pt-translate-header noprint',
1440 'dir' => $language->getDir(),
1441 'lang' => $language->getHtmlCode(),
1446 $output->addHTML( $header );
1448 if ( $wgTranslateKeepOutdatedTranslations ) {
1449 $groupId = $page->getMessageGroupId();
1451 $stats = MessageGroupStats::forItem( $groupId, $code );
1452 if ( $stats[MessageGroupStats::FUZZY] ) {
1454 $wrap = Html::rawElement(
1457 'class' =>
'mw-pt-translate-header',
1458 'dir' => $language->getDir(),
1459 'lang' => $language->getHtmlCode()
1461 '<span class="mw-translate-fuzzy">$1</span>'
1464 $output->wrapWikiMsg( $wrap, [
'tpt-translation-intro-fuzzy' ] );
1469 private static function isAllowedContentModel( Content $content, PageReference $page ): bool {
1470 $config = MediaWikiServices::getInstance()->getMainConfig();
1471 $allowedModels = $config->get(
'PageTranslationAllowedContentModels' );
1472 $contentModel = $content->getModel();
1473 $allowed = (bool)( $allowedModels[$contentModel] ??
false );
1476 if ( $allowed && !$content instanceof TextContent ) {
1477 LoggerFactory::getInstance( LogNames::MAIN )->error(
1478 'Expected {title} to have content of type TextContent, got {contentType}. ' .
1479 '$wgPageTranslationAllowedContentModels is incorrectly configured with a non-text content model.',
1481 'title' => (
string)$page,
1482 'contentType' => get_class( $content )
1496 $movePageSpec = $list[
'Movepage'] ??
null;
1499 if ( $movePageSpec ===
null ) {
1503 $list[
'Movepage'] = [
1504 'class' => MoveTranslatableBundleSpecialPage::class,
1507 'Translate:TranslatableBundleMover',
1508 'Translate:TranslatableBundleFactory',
1526 if ( $action ===
'read' ) {
1530 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance( CACHE_ANYTHING );
1531 $key = $cache->makeKey(
'pt-lock', sha1( $title->getPrefixedText() ) );
1532 if ( $cache->get( $key ) ===
'locked' ) {
1533 $result = [
'pt-locked-page' ];
1549 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1551 $isTranslationPage = TranslatablePage::isTranslationPage( $out->getTitle() );
1552 if ( !$isTranslationPage
1553 && !TranslatablePage::isSourcePage( $out->getTitle() )
1559 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1561 $out->isArticle() &&
1562 $nsInfo->hasSubpages( $out->getTitle()->getNamespace() )
1564 $ptext = $out->getTitle()->getPrefixedText();
1565 $links = explode(
'/', $ptext );
1566 if ( count( $links ) > 1 ) {
1567 array_pop( $links );
1568 if ( $isTranslationPage ) {
1570 array_pop( $links );
1575 $sitedir = $skin->getLanguage()->getDir();
1577 foreach ( $links as $link ) {
1578 $growinglink .= $link;
1580 $linkObj = Title::newFromText( $growinglink );
1582 if ( $linkObj && $linkObj->isKnown() ) {
1583 $getlink = $linker->makeKnownLink(
1584 SpecialPage::getTitleFor(
'MyLanguage', $growinglink ),
1591 $subpages .= $skin->msg(
'pipe-separator' )->escaped();
1593 $subpages .=
'< ';
1596 $subpages .= Html::rawElement(
'bdi', [
'dir' => $sitedir ], $getlink );
1602 $growinglink .=
'/';
1620 $title = $skin->getTitle();
1622 $code = $handle->getCode();
1623 $user = $skin->getUser();
1625 if ( TranslatablePage::isSourcePage( $title ) && !TranslatablePage::newFromTitle( $title )->isBroken() ) {
1626 if ( $user->isAllowed(
'pagetranslation' ) ) {
1627 $tabs[
'actions'][
'marktranslation'] = [
1628 'text' => $skin->msg(
'translate-ca-marktranslation' )->text(),
1629 'href' => SpecialPage::getTitleFor(
'PageTranslation' )->getLocalURL( [
1630 'target' => $title->getPrefixedText(),
1638 $page = TranslatablePage::isTranslationPage( $title );
1640 if ( !$page || $page->getSourceLanguageCode() === $code ) {
1644 if ( isset( $tabs[
'views'][
'edit'] ) ) {
1646 $tabs[
'views'][
'edit'][
'text'] = $skin->msg(
'tpt-tab-translate' )->text();
1647 $tabs[
'views'][
'edit'][
'href'] = $page->getTranslationUrl( $code );
1648 } elseif ( $user->isAllowed(
'translate' ) ) {
1649 $mwInstance = MediaWikiServices::getInstance();
1650 $namespaceProtection = $mwInstance->getMainConfig()->get( MainConfigNames::NamespaceProtection );
1651 $permissionManager = $mwInstance->getPermissionManager();
1653 !$permissionManager->userHasAllRights(
1654 $user, ...(array)( $namespaceProtection[ NS_TRANSLATIONS ] ?? [] )
1661 'text' => $skin->msg(
'tpt-tab-translate' )->text(),
1662 'href' => $page->getTranslationUrl( $code ),
1666 $viewsourcePos = array_keys( array_keys( $tabs[
'views'] ),
'viewsource',
true )[0] ??
null;
1668 if ( $viewsourcePos !==
null ) {
1671 array_splice( $tabs[
'views'], $viewsourcePos, 1, [
'translate' => $tab ] );
1675 $tabs[
'views'][
'translate'] = $tab;
1693 LinkTarget $oldLinkTarget,
1694 LinkTarget $newLinkTarget,
1695 UserIdentity $userIdentity,
1699 RevisionRecord $revisionRecord
1701 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $userIdentity );
1706 if ( defined(
'MEDIAWIKI_JOB_RUNNER' ) && $user->equals( FuzzyBot::getUser() ) ) {
1710 $oldTitle = Title::newFromLinkTarget( $oldLinkTarget );
1711 $newTitle = Title::newFromLinkTarget( $newLinkTarget );
1713 foreach ( [ $oldTitle, $newTitle ] as $title ) {
1716 if ( !$handle->isValid() || $handle->isDoc() ) {
1720 $group = $handle->getGroup();
1725 $language = $handle->getCode();
1728 if ( $language ===
'' ) {
1734 if ( $group !== $groupLast ) {
1735 $groupLast = $group;
1736 $page = TranslatablePage::newFromTitle( $group->getTitle() );
1737 self::updateTranslationPage( $page, $language, $user, 0, $reason );
1760 $title = $unit->getTitle();
1762 if ( !$handle->isValid() ) {
1766 $group = $handle->getGroup();
1771 $mwServices = MediaWikiServices::getInstance();
1774 $mwServices->getJobQueueGroup()->push(
1775 RebuildMessageGroupStatsJob::newRefreshGroupsJob( [ $group->getId() ] )
1779 if ( self::$isDeleteTranslatableBundleJobRunning ) {
1783 $target = $group->getTitle();
1784 $langCode = $handle->getCode();
1785 $fname = __METHOD__;
1787 $dbw = $mwServices->getConnectionProvider()->getPrimaryDatabase();
1788 $callback =
function () use (
1797 $translationPageTitle = $target->getSubpage( $langCode );
1800 if ( !$translationPageTitle || !$translationPageTitle->exists( IDBAccessObject::READ_LATEST ) ) {
1804 $dbw->startAtomic( $fname );
1806 $page = TranslatablePage::newFromTitle( $target );
1808 if ( !$handle->isDoc() ) {
1809 $unitTitle = $handle->getTitle();
1811 self::updateTranslationPage(
1812 $page, $langCode, $user, 0, $reason, RenderTranslationPageJob::ACTION_DELETE, $unitTitle
1816 $dbw->endAtomic( $fname );
1819 $dbw->onTransactionCommitOrIdle( $callback, __METHOD__ );
1827 foreach ( $titles as $index => $title ) {
1829 if ( Utilities::isTranslationPage( $handle ) ) {
1830 unset( $titles[ $index ] );
1840 foreach ( $titles as $index => $title ) {
1843 TranslatablePage::isSourcePage( $title ) ||
1844 Utilities::isTranslationPage( $handle )
1846 unset( $titles[ $index ] );
1851 public static function getSpecialManageMessageGroupSubscriptionsLink(
1856 'pagelink' => SpecialPage::getTitleFor(
'ManageMessageGroupSubscriptions' )->getPrefixedText()
1866 if ( !Utilities::isTranslationPage( $handle ) ) {
1869 $code = $handle->getCode();
1870 $categories = $linksUpdate->getParserOutput()->getCategoryNames();
1871 $editSummary = wfMessage(
1872 'translate-category-summary',
1873 $linksUpdate->getTitle()->getPrefixedText()
1874 )->inContentLanguage()->text();
1875 foreach ( $categories as $category ) {
1876 $categoryTitle = Title::makeTitle( NS_CATEGORY, $category );
1881 $categoryTranslationPage = TranslatablePage::isTranslationPage( $categoryTitle );
1883 $categoryTranslationPage
1884 && $categoryHandle->getCode() == $code
1885 && !$categoryTitle->exists()
1887 self::updateTranslationPage(
1888 $categoryTranslationPage,
1890 FuzzyBot::getUser(),
1893 RenderTranslationPageJob::ACTION_CATEGORIZATION
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'), $services->get( 'Translate:MessageGroupFactory'),);}, '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->get( 'Translate:MessageGroupFactory'), $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:MessageGroupFactory'=> static function(MediaWikiServices $services):MessageGroupFactory { return new MessageGroupFactory($services->get( 'Translate:MessageGroupTypeRegistry'));}, '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(), $services->getNotificationService(), 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();}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageGroupTypeRegistry'=> static function():MessageGroupTypeRegistry { return new MessageGroupTypeRegistry();}, '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
Wraps the translatable page sections into a message group.
Hooks for page translation.
static tpSyntaxCheckForEditContent( $context, $content, $status, $summary)
Display nice error when editing content.
static tpSyntaxCheck(RenderedRevision $renderedRevision, UserIdentity $user, CommentStoreComment $summary, $flags, Status $hookStatus)
When attempting to save, last resort.
static onReplaceTextFilterPageTitlesForRename(array &$titles)
Removes translatable and translation pages from the list of titles to be renamed Hook: ReplaceTextFil...
static bool $isDeleteTranslatableBundleJobRunning
State flag used by DeleteTranslatableBundleJob for performance optimizations.
static onMovePageTranslationUnits(LinkTarget $oldLinkTarget, LinkTarget $newLinkTarget, UserIdentity $userIdentity, int $oldid, int $newid, string $reason, RevisionRecord $revisionRecord)
Hook to update source and destination translation pages on moving translation units Hook: PageMoveCom...
static replaceMovePage(&$list)
Hook: SpecialPage_initList.
static onTitleGetEditNotices(Title $title, int $oldid, array &$notices)
Display an edit notice for translatable source pages if it's enabled Hook: TitleGetEditNotices.
static onParserGetVariableValueSwitch(Parser $parser, array &$variableCache, string $magicWordId, ?string &$ret, PPFrame $frame)
Hook: ParserGetVariableValueSwitch.
static onGetUserPermissionsErrorsExpensive(Title $title, User $user, $action, &$result)
Prevent creation of orphan translation units in Translations namespace.
static preprocessTagPage( $wikitextParser, &$text, $state)
Hook: ParserBeforePreprocess.
static onDeleteTranslationUnit(WikiPage $unit, User $user, $reason, $id, $content, $logEntry)
Hook to update translation page on deleting a translation unit Hook: ArticleDeleteComplete.
static preventDirectEditing(Title $title, User $user, $action, &$result)
Prevent editing of translation pages directly.
static renderTagPage( $wikitextParser, &$text, $state)
Hook: ParserBeforeInternalParse.
static onSectionSave(WikiPage $wikiPage, User $user, TextContent $content, $summary, $minor, $flags, MessageHandle $handle)
This is triggered after an edit to translation unit page.
static onSkinTemplateNavigation__Universal(Skin $skin, array &$tabs)
Converts the edit tab (if exists) for translation pages to translate tab, and adds a "mark for transl...
static fetchTranslatableTemplateAndTitle(?LinkTarget $contextLink, ?LinkTarget $templateLink, bool &$skip, ?RevisionRecord &$revRecord)
This sets &$revRecord to the revision of transcluded page translation if it exists,...
static onBeforePageDisplay(OutputPage $out, Skin $skin)
Hook: BeforePageDisplay.
static translatablePageHeader( $article, &$outputDone, &$pcache)
Hook: ArticleViewHeader.
static onPageContentLanguage(Title $title, &$pageLang)
Set the right page content language for translated pages ("Page/xx").
static lockedPagesCheck(Title $title, User $user, $action, &$result)
Hook: getUserPermissionsErrorsExpensive.
static updateTranstagOnNullRevisions(RevisionRecord $rev)
Page moving and page protection (and possibly other things) creates null revisions.
static onVisualEditorBeforeEditor(OutputPage $out, Skin $skin)
Hook: onVisualEditorBeforeEditor.
static replaceSubtitle(&$subpages, ?Skin $skin, OutputPage $out)
Hook: SkinSubPageSubtitle.
static formatLanguageLink(array &$link, Title $linkTitle, Title $pageTitle, OutputPage $out)
Hooks: SkinTemplateGetLanguageLink.
static onLinksUpdateComplete(LinksUpdate $linksUpdate)
Create any redlinked categories marked for translation Hook: LinksUpdateComplete.
static onGetMagicVariableIDs(&$variableIDs)
Hook: GetMagicVariableIDs.
static languages( $data, $params, $parser)
static addLanguageLinks(Title $title, array &$languageLinks)
Hooks: LanguageLinks.
static disableDelete( $article, $out, &$reason)
Redirects the delete action to our own for translatable pages.
static onReplaceTextFilterPageTitlesForEdit(array &$titles)
Removes translation pages from the list of page titles to be edited Hook: ReplaceTextFilterPageTitles...
static preventMoves(Title $oldTitle, Title $newTitle, StatusValue $status)
Prevent moving translatable pages, translation pages, or message bundles by any means other than our ...
static addTranstagAfterSave(WikiPage $wikiPage, UserIdentity $userIdentity, string $summary, int $flags, RevisionRecord $revisionRecord, EditResult $editResult)
Hook: PageSaveComplete.
Represents any kind of failure to parse a translatable page source code.
Mixed bag of methods related to translatable pages.
addReadyTag(int $revision)
Adds a tag which indicates that this page source is ready for marking for translation.
getMessageGroupId()
@inheritDoc
getMarkedTag()
Returns the latest revision which has marked tag, if any.
getTranslationPages()
@inheritDoc
getReadyTag()
Returns the latest revision which has ready tag, if any.
getTranslationUrl( $code=false)
Produces a link to translation view of a translation page.
getMessageGroup()
Returns MessageGroup used for translating this page.
getPageDisplayTitle(string $languageCode)
Get translated page title.
static isTranslationPage(Title $title)
This class represents one translation unit in a translatable page.