5namespace MediaWiki\Extension\Translate\PageTranslation;
8use MediaWiki\Category\Category;
9use MediaWiki\CommentStore\CommentStoreComment;
10use MediaWiki\Config\Config;
11use MediaWiki\Content\Content;
12use MediaWiki\Content\TextContent;
13use MediaWiki\Context\IContextSource;
14use MediaWiki\Context\RequestContext;
15use MediaWiki\Deferred\DeferredUpdates;
16use MediaWiki\Deferred\LinksUpdate\LinksUpdate;
17use MediaWiki\Exception\UserBlockedError;
27use MediaWiki\Html\Html;
28use MediaWiki\Language\Language;
29use MediaWiki\Language\LanguageCode;
30use MediaWiki\Language\LanguageNameUtils;
31use MediaWiki\Linker\LinkTarget;
32use MediaWiki\Logger\LoggerFactory;
33use MediaWiki\Logging\ManualLogEntry;
34use MediaWiki\MainConfigNames;
35use MediaWiki\MediaWikiServices;
36use MediaWiki\Output\OutputPage;
37use MediaWiki\Page\Article;
38use MediaWiki\Page\PageIdentity;
39use MediaWiki\Page\PageReference;
40use MediaWiki\Page\WikiPage;
41use MediaWiki\Parser\Parser;
42use MediaWiki\Parser\ParserOutput;
43use MediaWiki\Parser\ParserOutputFlags;
44use MediaWiki\Parser\PPFrame;
45use MediaWiki\ResourceLoader\Context;
46use MediaWiki\Revision\MutableRevisionRecord;
47use MediaWiki\Revision\RenderedRevision;
48use MediaWiki\Revision\RevisionRecord;
49use MediaWiki\Revision\SlotRecord;
50use MediaWiki\Skin\Skin;
51use MediaWiki\SpecialPage\SpecialPage;
52use MediaWiki\Status\Status;
53use MediaWiki\Storage\EditResult;
54use MediaWiki\StubObject\StubUserLang;
55use MediaWiki\Title\Title;
56use MediaWiki\User\User;
57use MediaWiki\User\UserIdentity;
60use Wikimedia\Rdbms\IDBAccessObject;
61use Wikimedia\ScopedCallback;
71 private const PAGEPROP_HAS_LANGUAGES_TAG =
'translate-has-languages-tag';
73 public static $allowTargetEdit =
false;
77 public static $renderingContext =
false;
79 private static $languageLinkData = [];
88 public static function renderTagPage( $wikitextParser, &$text, $state ): void {
89 if ( $text === null ) {
97 if ( $wikitextParser->getOptions()->getInterfaceMessage() ) {
103 if ( $wikitextParser->getOptions()->getIsSectionPreview() ) {
104 $translatablePageParser = Services::getInstance()->getTranslatablePageParser();
105 $text = $translatablePageParser->cleanupTags( $text );
109 $title = MediaWikiServices::getInstance()
111 ->castFromPageReference( $wikitextParser->getPage() );
121 $wikitextParser->getOutput()->setUnsortedPageProperty(
'translate-is-translation' );
124 self::$renderingContext =
true;
126 $name = $page->getPageDisplayTitle( $code );
128 $name = $wikitextParser->recursivePreprocess( $name );
130 $langConv = MediaWikiServices::getInstance()->getLanguageConverterFactory()
131 ->getLanguageConverter( $wikitextParser->getTargetLanguage() );
132 $name = $langConv->convert( $name );
133 $wikitextParser->getOutput()->setDisplayTitle( $name );
135 self::$renderingContext =
false;
136 }
catch ( Exception $e ) {
138 'T302754 Failed to set display title for page {title}',
140 'title' => $title->getPrefixedDBkey(),
142 'pageid' => $title->getId(),
151 'languagecode' => $code,
152 'messagegroupid' => $page->getMessageGroupId(),
153 'sourcepagetitle' => [
154 'namespace' => $page->getTitle()->getNamespace(),
155 'dbkey' => $page->getTitle()->getDBkey()
159 $wikitextParser->getOutput()->setExtensionData(
'translate-translation-page', $extensionData );
161 $wikitextParser->getOutput()->setOutputFlag( ParserOutputFlags::NO_SECTION_EDIT_LINKS );
172 $translatablePageParser =
Services::getInstance()->getTranslatablePageParser();
174 if ( $translatablePageParser->containsMarkup( $text ) ) {
176 $parserOutput = $translatablePageParser->parse( $text );
178 $text = $parserOutput->sourcePageTextForRendering(
179 $wikitextParser->getTargetLanguage()
182 wfDebug(
'ParsingFailure caught; expected' );
188 $text = $unit->getTextForTrans();
204 ?LinkTarget $contextLink,
205 ?LinkTarget $templateLink,
207 ?RevisionRecord &$revRecord
209 if ( !$templateLink ) {
213 $templateTitle = Title::newFromLinkTarget( $templateLink );
215 $templateTranslationPage = TranslatablePage::isTranslationPage( $templateTitle );
216 if ( $templateTranslationPage ) {
219 $revRecord = $templateTranslationPage->getRevisionRecordWithFallback();
222 LoggerFactory::getInstance( LogNames::MAIN )->warning(
223 "T323863: Could not fetch any revision record for '{groupid}'",
224 [
'groupid' => $templateTranslationPage->getMessageGroupId() ]
230 if ( !TranslatablePage::isSourcePage( $templateTitle ) ) {
234 $translatableTemplatePage = TranslatablePage::newFromTitle( $templateTitle );
236 if ( !( $translatableTemplatePage->supportsTransclusion() ??
false ) ) {
241 $store = MediaWikiServices::getInstance()->getRevisionStore();
243 if ( $contextLink ) {
245 $templateTranslationTitle = $templateTitle->getSubpage(
246 Title::newFromLinkTarget( $contextLink )->getPageLanguage()->getCode()
249 if ( $templateTranslationTitle ) {
250 if ( $templateTranslationTitle->exists() ) {
252 $revRecord = $store->getRevisionByTitle( $templateTranslationTitle );
257 $revRecord =
new MutableRevisionRecord( $templateTranslationTitle );
265 $sourceTemplateTitle = $templateTitle->getSubpage(
266 $translatableTemplatePage->getMessageGroup()->getSourceLanguage()
268 if ( $sourceTemplateTitle && $sourceTemplateTitle->exists() ) {
269 $revRecord = $store->getRevisionByTitle( $sourceTemplateTitle );
282 if ( TranslatablePage::isTranslationPage( $title ) ) {
283 [ , $code ] = Utilities::figureMessage( $title->getText() );
284 $pageLang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $code );
296 if ( TranslatablePage::isSourcePage( $title ) ) {
297 $msg = wfMessage(
'translate-edit-tag-warning' )->inContentLanguage();
298 if ( !$msg->isDisabled() ) {
299 $notices[
'translate-tag'] = $msg->parseAsBlock();
302 $notices[] = Html::warningBox(
303 wfMessage(
'tps-edit-sourcepage-text' )->parse(),
304 'translate-edit-documentation'
309 $request = RequestContext::getMain()->getRequest();
310 if ( $request->getVal(
'action' ) ===
'visualeditor' &&
311 $request->getVal(
'paction' ) !==
'wikitext'
313 $notices[] = Html::warningBox(
314 wfMessage(
'tps-edit-sourcepage-ve-warning-limited-text' )->parse(),
315 'translate-edit-documentation'
327 $title = $out->getTitle();
328 if ( $title->isSpecialPage() ) {
332 $isSource = TranslatablePage::isSourcePage( $title );
333 $isTranslation = TranslatablePage::isTranslationPage( $title );
335 if ( $isSource || $isTranslation ) {
336 $out->addModules(
'ext.translate.pagetranslation.uls' );
340 $out->addModuleStyles(
'ext.translate.edit.documentation.styles' );
343 $out->addModuleStyles(
'ext.translate' );
345 $out->addJsConfigVars(
'wgTranslatePageTranslation', $isTranslation ?
'translation' :
'source' );
347 $groupId = TranslatablePage::getMessageGroupIdFromTitle( $title );
348 $out->addJsConfigVars(
'wgTranslatePageTranslationGroup', $groupId );
350 $priorityLanguages = Services::getInstance()->getMessageGroupMetadata()->get( $groupId,
'prioritylangs' );
351 if ( $priorityLanguages ) {
352 $priorityLanguages = explode(
',', $priorityLanguages );
354 $priorityLanguages = [];
356 $out->addJsConfigVars(
'wgTranslatePriorityLanguages', $priorityLanguages );
358 $viewTranslatablePage = Services::getInstance()->getTranslatablePageView();
359 $user = $out->getUser();
360 if ( $viewTranslatablePage->canDisplayTranslationSettingsBanner( $title, $user ) ) {
361 $out->addModules(
'ext.translate.uls.translation.banner' );
373 return !TranslatablePage::isTranslationPage( $out->getTitle() );
389 TextContent $content,
396 if ( $user->equals( FuzzyBot::getUser() ) ) {
406 $page = TranslatablePage::newFromTitle( $group->getTitle() );
409 if ( !$handle->
isDoc() ) {
411 DeferredUpdates::addCallableUpdate(
412 function () use ( $page, $code, $user, $flags, $summary, $handle ) {
414 self::updateTranslationPage( $page, $code, $user, $flags, $summary,
null, $unitTitle );
420 private static function updateTranslationPage(
426 ?
string $triggerAction =
null,
427 ?Title $unitTitle =
null
429 $source = $page->getTitle();
430 $target = $source->getSubpage( $code );
431 $mwInstance = MediaWikiServices::getInstance();
434 $flags &= ~EDIT_NEW & ~EDIT_UPDATE;
437 $unitTitleText = $unitTitle ? $unitTitle->getPrefixedText() :
null;
438 $job = RenderTranslationPageJob::newJob( $target, $triggerAction, $unitTitleText );
440 if ( !$user->equals( FuzzyBot::getUser() ) ) {
441 $session = RequestContext::getMain()->exportSession();
443 $job->setUser( $user, $session );
444 $job->setSummary( $summary );
445 $job->setFlags( $flags );
446 $mwInstance->getJobQueueGroup()->push( $job );
450 $wikiPageFactory = $mwInstance->getWikiPageFactory();
451 foreach ( $pages as $title ) {
452 if ( $title->equals( $target ) ) {
457 $wikiPage = $wikiPageFactory->newFromTitle( $title );
458 $wikiPage->doPurge();
460 $sourceWikiPage = $wikiPageFactory->newFromTitle( $source );
461 $sourceWikiPage->doPurge();
469 $variableIDs[] =
'translatablepage';
477 array &$variableCache,
482 switch ( $magicWordId ) {
483 case 'translatablepage':
484 $pageStatus = self::getTranslatablePageStatus( $parser->getPage() );
485 $ret = $pageStatus !==
null ? $pageStatus[
'page']->getTitle()->getPrefixedText() :
'';
486 $variableCache[$magicWordId] = $ret;
497 public static function languages( $data, $params, $parser ) {
498 global $wgPageTranslationLanguageList;
500 if ( $wgPageTranslationLanguageList ===
'sidebar-only' ) {
504 self::$renderingContext =
true;
505 $context =
new ScopedCallback(
static function () {
506 self::$renderingContext =
false;
511 $parser->getOutput()->setUnsortedPageProperty( self::PAGEPROP_HAS_LANGUAGES_TAG );
513 $currentPage = $parser->getPage();
514 $pageStatus = self::getTranslatablePageStatus( $currentPage );
515 if ( !$pageStatus ) {
519 $page = $pageStatus[
'page' ];
520 $status = $pageStatus[
'languages' ];
527 $userLang = $parser->getOptions()->getUserLangObj();
528 $userLangCode = $userLang->getCode();
532 $sourceLanguage = $pageTitle->getPageLanguage()->getCode();
535 $langFactory = MediaWikiServices::getInstance()->getLanguageFactory();
536 foreach ( $status as $code => $percent ) {
538 $name = Utilities::getLanguageName( $code, LanguageNameUtils::AUTONYMS );
541 $suffix = ( $code === $sourceLanguage ) ?
'' :
"/$code";
542 $targetTitleString = $pageTitle->getDBkey() . $suffix;
543 $subpage = Title::makeTitle( $pageTitle->getNamespace(), $targetTitleString );
546 if ( $code === $userLangCode ) {
547 $classes[] =
'mw-pt-languages-ui';
550 $linker = $parser->getLinkRenderer();
551 $lang = $langFactory->getLanguage( $code );
552 if ( $currentPage->isSamePageAs( $subpage ) ) {
553 $classes[] =
'mw-pt-languages-selected';
554 $classes = array_merge( $classes, self::tpProgressIcon( (
float)$percent ) );
557 'lang' => $lang->getHtmlCode(),
558 'dir' => $lang->getDir(),
561 $contents = Html::element(
'span', $attribs, $name );
562 } elseif ( $subpage->isKnown() ) {
564 if ( !is_string( $pagename ) ) {
565 $pagename = $subpage->getPrefixedText();
568 $classes = array_merge( $classes, self::tpProgressIcon( (
float)$percent ) );
570 $title = wfMessage(
'tpt-languages-nonzero' )
571 ->page( $parser->getPage() )
572 ->inLanguage( $userLang )
573 ->params( $pagename )
574 ->numParams( 100 * $percent )
579 'lang' => $lang->getHtmlCode(),
580 'dir' => $lang->getDir(),
583 $contents = $linker->makeKnownLink( $subpage, $name, $attribs );
588 $specialTranslateTitle = SpecialPage::getTitleFor(
'Translate' );
598 'title' => wfMessage(
'tpt-languages-zero' )
599 ->page( $parser->getPage() )
600 ->inLanguage( $userLang )
603 'lang' => $lang->getHtmlCode(),
604 'dir' => $lang->getDir(),
606 $contents = $linker->makeKnownLink( $specialTranslateTitle, $name, $attribs, $params );
608 $languages[ $name ] = Html::rawElement(
'li', [], $contents );
613 $languages = array_values( $languages );
614 $languages = implode(
"\n", $languages );
616 $out = Html::openElement(
'div', [
617 'class' =>
'mw-pt-languages noprint navigation-not-searchable',
618 'lang' => $userLang->getHtmlCode(),
619 'dir' => $userLang->getDir()
621 $out .= Html::rawElement(
'div', [
'class' =>
'mw-pt-languages-label' ],
622 wfMessage(
'tpt-languages-legend' )
623 ->page( $parser->getPage() )
624 ->inLanguage( $userLang )
627 $out .= Html::rawElement(
629 [
'class' =>
'mw-pt-languages-list' ],
632 $out .= Html::closeElement(
'div' );
634 $parser->getOutput()->addModuleStyles( [
635 'ext.translate.tag.languages',
647 private static function tpProgressIcon(
float $percent ) {
648 $classes = [
'mw-pt-progress' ];
650 if ( $percent < 15 ) {
651 $classes[] =
'mw-pt-progress--low';
652 } elseif ( $percent < 70 ) {
653 $classes[] =
'mw-pt-progress--med';
654 } elseif ( $percent < 100 ) {
655 $classes[] =
'mw-pt-progress--high';
657 $classes[] =
'mw-pt-progress--complete';
666 private static function getTranslatablePageStatus( ?PageReference $pageReference ): ?array {
667 if ( $pageReference === null ) {
670 $title = Title::newFromPageReference( $pageReference );
672 $page = TranslatablePage::newFromTitle( $title );
674 $page = TranslatablePage::isTranslationPage( $title );
677 if ( $page ===
false || $page->
getMarkedTag() ===
null ) {
681 $status = $page->getTranslationPercentages();
686 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
688 $priorityLanguages = $messageGroupMetadata->get( $page->
getMessageGroupId(),
'prioritylangs' );
689 if ( $priorityLanguages !==
false && $priorityLanguages !==
'' ) {
690 $status += array_fill_keys( explode(
',', $priorityLanguages ), 0 );
695 'languages' => $status
705 global $wgPageTranslationLanguageList;
707 if ( $wgPageTranslationLanguageList ===
'tag-only' ) {
711 if ( $wgPageTranslationLanguageList ===
'sidebar-fallback' ) {
712 $pageProps = MediaWikiServices::getInstance()->getPageProps();
713 $languageProp = $pageProps->getProperties( $title, self::PAGEPROP_HAS_LANGUAGES_TAG );
714 if ( $languageProp !== [] ) {
721 $status = self::getTranslatablePageStatus( $title );
726 self::$renderingContext =
true;
727 $context =
new ScopedCallback(
static function () {
728 self::$renderingContext =
false;
731 $page = $status[
'page' ];
732 $languages = $status[
'languages' ];
733 $mwServices = MediaWikiServices::getInstance();
734 $en = $mwServices->getLanguageFactory()->getLanguage(
'en' );
737 $lb = $mwServices->getLinkBatchFactory()->newLinkBatch();
738 foreach ( array_keys( $languages ) as $code ) {
739 $title = $page->getTitle()->getSubpage( $code );
740 $lb->addObj( $title );
743 $languageNameUtils = $mwServices->getLanguageNameUtils();
744 foreach ( $languages as $code => $percentage ) {
745 $title = $page->getTitle()->getSubpage( $code );
746 $placeholderValue =
"$code-x-pagetranslation:{$title->getPrefixedText()}";
747 $translatedName = $page->getPageDisplayTitle( $code ) ?: $title->getPrefixedText();
749 if ( $title->exists() ) {
750 $href = $title->getLocalURL();
751 $classes = self::tpProgressIcon( (
float)$percentage );
752 $titleAttribute = wfMessage(
'tpt-languages-nonzero' )
753 ->params( $translatedName )
754 ->numParams( 100 * $percentage );
756 $href = SpecialPage::getTitleFor(
'Translate' )->getLocalURL( [
757 'group' => $page->getMessageGroupId(),
760 $classes = [
'mw-pt-progress--none' ];
761 $titleAttribute = wfMessage(
'tpt-languages-zero' );
764 self::$languageLinkData[ $placeholderValue ] = [
767 'classes' => $classes,
768 'autonym' => $en->ucfirst( $languageNameUtils->getLanguageName( $code ) ),
769 'title' => $titleAttribute,
773 $languageLinks[] = $placeholderValue;
790 $data = self::$languageLinkData[$link[
'text']] ??
null;
795 $link[
'class'] .=
' interwiki-pagetranslation ' . implode(
' ', $data[
'classes'] );
796 $link[
'href'] = $data[
'href'];
797 $link[
'text'] = $data[
'autonym'];
798 $link[
'title'] = $data[
'title']->inLanguage( $out->getLanguage()->getCode() )->text();
799 $link[
'lang'] = LanguageCode::bcp47( $data[
'language'] );
800 $link[
'hreflang'] = LanguageCode::bcp47( $data[
'language'] );
802 $out->addModuleStyles(
'ext.translate.tag.languages' );
820 $syntaxErrorStatus = self::tpSyntaxError( $context->getTitle(), $content );
822 if ( $syntaxErrorStatus ) {
823 $status->merge( $syntaxErrorStatus );
824 return $syntaxErrorStatus->isGood();
830 private static function tpSyntaxError( ?PageIdentity $page, ?Content $content ): ?Status {
831 if ( !$page || !self::isAllowedContentModel( $content, $page ) ) {
835 '@phan-var TextContent $content';
836 $text = $content->getText();
839 $text = TextContent::normalizeLineEndings( $text );
840 $status = Status::newGood();
841 $parser = Services::getInstance()->getTranslatablePageParser();
842 if ( $parser->containsMarkup( $text ) ) {
844 $parser->parse( $text );
845 }
catch ( ParsingFailure $e ) {
846 $status->fatal( $e->getMessageSpecification() );
865 RenderedRevision $renderedRevision,
867 CommentStoreComment $summary,
871 $content = $renderedRevision->getRevision()->getContent( SlotRecord::MAIN );
873 $status = self::tpSyntaxError(
874 $renderedRevision->getRevision()->getPage(),
879 $hookStatus->merge( $status );
880 return $status->isGood();
898 UserIdentity $userIdentity,
901 RevisionRecord $revisionRecord,
902 EditResult $editResult
904 $content = $wikiPage->getContent();
907 if ( !self::isAllowedContentModel( $content, $wikiPage ) ) {
911 '@phan-var TextContent $content';
912 $text = $content->getText();
914 $parser = Services::getInstance()->getTranslatablePageParser();
915 if ( $parser->containsMarkup( $text ) ) {
917 $page = TranslatablePage::newFromTitle( $wikiPage->getTitle() );
922 $tpStatusUpdater = Services::getInstance()->getTranslatablePageStore();
923 $tpStatusUpdater->performStatusUpdate( $wikiPage->getTitle() );
941 $parentId = $rev->getParentId();
942 if ( $parentId === 0 || $parentId ===
null ) {
947 $prevRev = MediaWikiServices::getInstance()
948 ->getRevisionLookup()
949 ->getRevisionById( $parentId );
951 if ( !$prevRev || !$rev->hasSameContent( $prevRev ) ) {
956 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
957 $bundle = $bundleFactory->getBundle( $rev->getPage() );
960 $bundleStore = $bundleFactory->getStore( $bundle );
961 $bundleStore->handleNullRevisionInsert( $bundle, $rev );
987 if ( !$handle->isPageTranslation() || $action ===
'read' ) {
994 if ( $handle->isValid() ) {
995 $group = $handle->getGroup();
996 $groupId = $group->getId();
997 $permissionTitleCheck =
null;
1000 $permissionTitleCheck = $group->getTitle();
1004 $permissionTitleCheck = Title::newFromID( $group->getBundlePageId() );
1007 if ( $permissionTitleCheck ) {
1008 if ( $handle->getCode() === $group->getSourceLanguage() && !$user->equals( FuzzyBot::getUser() ) ) {
1013 $allowedActionList = [
1014 'read',
'deletedtext',
'deletedhistory',
1015 'deleterevision',
'suppressrevision',
'viewsuppressed',
1019 if ( !in_array( $action, $allowedActionList ) ) {
1020 $result = [
'tpt-cant-edit-source-language', $permissionTitleCheck ];
1025 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1026 if ( $permissionManager->isBlockedFrom( $user, $permissionTitleCheck ) ) {
1027 $block = $user->getBlock();
1029 $error =
new UserBlockedError( $block, $user );
1030 $errorMessage = $error->getMessageObject();
1031 $result = array_merge( [ $errorMessage->getKey() ], $errorMessage->getParams() );
1035 if ( $action ===
'create' && $permissionTitleCheck->inNamespace( NS_CATEGORY ) ) {
1036 $renderedPage = $permissionTitleCheck->getSubpage( $handle->getCode() );
1037 if ( !$renderedPage->exists() ) {
1038 $cat = Category::newFromTitle( $renderedPage );
1039 if ( $cat->getMemberCount() === 0 ) {
1040 if ( !$permissionManager->userCan(
'translate-empty-category', $user, $renderedPage ) ) {
1041 $result = [
'tpt-create-empty-category', $renderedPage ];
1052 if ( $action !==
'create' ) {
1056 if ( !$handle->isValid() ) {
1062 $translatablePage = self::checkTranslatablePageSlow( $title );
1063 MediaWikiServices::getInstance()->getStatsFactory()
1064 ->withComponent(
'Translate' )
1065 ->getCounter(
'slow_translatable_page_check' )
1066 ->setLabel(
'valid', $translatablePage ?
'yes' :
'no' )
1069 if ( $translatablePage ) {
1070 $groupId = $translatablePage->getMessageGroupId();
1077 $error = self::getTranslationRestrictions( $handle, $groupId );
1078 $result = $error ?: $result;
1079 return $error === [];
1083 LoggerFactory::getInstance( LogNames::MAIN )->info(
1084 'Unknown translation page: {title}',
1085 [
'title' => $title->getPrefixedDBkey() ]
1087 $result = [
'tpt-unknown-page' ];
1091 private static function checkTranslatablePageSlow( LinkTarget $unit ): ?
TranslatablePage {
1093 $translationPageTitle = Title::newFromText(
1094 $parts[
'sourcepage' ] .
'/' . $parts[
'language' ]
1096 if ( !$translationPageTitle ) {
1100 $translatablePage = TranslatablePage::isTranslationPage( $translationPageTitle );
1101 if ( !$translatablePage ) {
1105 $factory = Services::getInstance()->getTranslationUnitStoreFactory();
1106 $store = $factory->getReader( $translatablePage->getPageIdentity() );
1107 $units = $store->getNames();
1109 if ( !in_array( $parts[
'section' ], $units ) ) {
1113 return $translatablePage;
1123 private static function getTranslationRestrictions( MessageHandle $handle,
MessageGroup|
string $group ) {
1124 global $wgTranslateDocumentationLanguageCode;
1127 if ( $handle->getCode() === $wgTranslateDocumentationLanguageCode ) {
1131 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
1133 $force = $messageGroupMetadata->get( $group,
'priorityforce' );
1134 if ( $force !==
'on' ) {
1139 $languages = $messageGroupMetadata->get( $group,
'prioritylangs' );
1140 $reason = $messageGroupMetadata->get( $group,
'priorityreason' );
1141 if ( !$languages ) {
1143 return [
'tpt-translation-restricted-no-priority-languages', $reason ];
1145 return [
'tpt-translation-restricted-no-priority-languages-no-reason' ];
1148 $filter = array_flip( explode(
',', $languages ) );
1149 if ( !isset( $filter[$handle->getCode()] ) ) {
1151 return [
'tpt-translation-restricted', $reason ];
1154 return [
'tpt-translation-restricted-no-reason' ];
1170 if ( self::$allowTargetEdit ) {
1175 'read',
'deletedtext',
'deletedhistory',
1176 'deleterevision',
'suppressrevision',
'viewsuppressed',
1179 'translate-empty-category'
1181 $needsPageTranslationRight = in_array( $action, [
'delete',
'undelete' ] );
1182 if ( in_array( $action, $inclusionList ) ||
1183 ( $needsPageTranslationRight && $user->isAllowed(
'pagetranslation' ) )
1188 $page = TranslatablePage::isTranslationPage( $title );
1190 $mwService = MediaWikiServices::getInstance();
1191 if ( $needsPageTranslationRight ) {
1192 $context = RequestContext::getMain();
1193 $statusFormatter = $mwService->getFormatterFactory()->getStatusFormatter( $context );
1194 $permissionError = $mwService->getPermissionManager()
1195 ->newFatalPermissionDeniedStatus(
'pagetranslation', $context );
1196 $result = $statusFormatter->getMessage( $permissionError );
1200 [ , $code ] = Utilities::figureMessage( $title->getText() );
1204 'tpt-source-mirror',
1205 ':' . $page->
getTitle()->getPrefixedText()
1210 $translationUrl = $mwService->getUrlUtils()->expand(
1216 ':' . $page->
getTitle()->getPrefixedText(),
1232 public static function preventMoves( Title $oldTitle, Title $newTitle, StatusValue $status ) {
1233 if ( self::$allowTargetEdit ) {
1237 if ( TranslatablePage::newFromTitle( $oldTitle )->getMarkedTag() !==
null ) {
1238 $status->fatal(
'tpt-manual-move-source', $oldTitle->getPrefixedText() );
1242 if ( $oldTitle->getContentModel() === MessageBundleContent::CONTENT_MODEL_ID ) {
1243 $status->fatal(
'mb-manual-move', $oldTitle->getPrefixedText() );
1246 $tp = TranslatablePage::isTranslationPage( $oldTitle );
1249 $status->fatal(
'tpt-manual-move-translation', $tp->getTitle()->getPrefixedText() );
1264 $page = $article->getPage();
1266 $bundle = Services::getInstance()->getTranslatableBundleFactory()->getBundle( $page );
1267 $isDeletableBundle = $bundle && $bundle->isDeletable();
1268 if ( $isDeletableBundle || TranslatablePage::isTranslationPage( $title ) ) {
1269 $new = SpecialPage::getTitleFor(
1270 'PageTranslationDeletePage',
1271 $title->getPrefixedText()
1273 $out->redirect( $new->getFullURL() );
1287 if ( $article->getOldID() ) {
1291 $articleTitle = $article->getTitle();
1292 $transPage = TranslatablePage::isTranslationPage( $articleTitle );
1293 $context = $article->getContext();
1295 self::translationPageHeader( $context, $transPage );
1297 $viewTranslatablePage = Services::getInstance()->getTranslatablePageView();
1298 $user = $context->getUser();
1299 if ( $viewTranslatablePage->canDisplayTranslationSettingsBanner( $articleTitle, $user ) ) {
1300 $output = $context->getOutput();
1301 $pageUrl = SpecialPage::getTitleFor(
'PageTranslation' )->getFullURL( [
1303 'target' => $articleTitle->getPrefixedDBkey(),
1307 $context->msg(
'pt-cta-mark-translation', $pageUrl )->parse(),
1308 'translate-cta-pt-mark'
1312 self::sourcePageHeader( $context );
1317 private static function sourcePageHeader( IContextSource $context ) {
1318 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1320 $language = $context->getLanguage();
1321 $title = $context->getTitle();
1323 $page = TranslatablePage::newFromTitle( $title );
1327 $latest = $title->getLatestRevID();
1330 if ( $marked && $context->getUser()->isAllowed(
'translate' ) ) {
1331 $actions[] = self::getTranslateLink( $context, $page,
null );
1334 $hasChanges = $ready === $latest && $marked !== $latest;
1335 if ( $hasChanges ) {
1336 $diffUrl = $title->getFullURL( [
'oldid' => $marked,
'diff' => $latest ] );
1338 if ( $context->getUser()->isAllowed(
'pagetranslation' ) ) {
1339 $pageTranslation = SpecialPage::getTitleFor(
'PageTranslation' );
1340 $params = [
'target' => $title->getPrefixedText(),
'do' =>
'mark' ];
1342 if ( $marked ===
null ) {
1344 $linkDesc = $context->msg(
'translate-tag-markthis' )->text();
1345 $actions[] = $linker->makeKnownLink( $pageTranslation, $linkDesc, [], $params );
1347 $markUrl = $pageTranslation->getFullURL( $params );
1348 $actions[] = $context->msg(
'translate-tag-markthisagain', $diffUrl, $markUrl )
1352 $actions[] = $context->msg(
'translate-tag-hasnew', $diffUrl )->parse();
1356 if ( !count( $actions ) ) {
1360 $header = Html::rawElement(
1363 'class' =>
'mw-pt-translate-header noprint nomobile',
1364 'dir' => $language->getDir(),
1365 'lang' => $language->getHtmlCode(),
1367 $language->semicolonList( $actions )
1370 $context->getOutput()->addHTML( $header );
1373 private static function getTranslateLink(
1374 IContextSource $context,
1375 TranslatablePage $page,
1378 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1380 return $linker->makeKnownLink(
1381 SpecialPage::getTitleFor(
'Translate' ),
1382 $context->msg(
'translate-tag-translate-link-desc' )->text(),
1385 'group' => $page->getMessageGroupId(),
1386 'language' => $langCode,
1389 'action_source' =>
'translate_page'
1394 private static function translationPageHeader( IContextSource $context, TranslatablePage $page ) {
1395 global $wgTranslateKeepOutdatedTranslations;
1397 $title = $context->getTitle();
1398 if ( !$title->exists() ) {
1402 [ , $code ] = Utilities::figureMessage( $title->getText() );
1405 $pers = $page->getTranslationPercentages();
1407 if ( isset( $pers[$code] ) ) {
1408 $per = $pers[$code] * 100;
1411 $language = $context->getLanguage();
1412 $output = $context->getOutput();
1414 if ( $page->getSourceLanguageCode() === $code ) {
1416 $msg = self::getTranslateLink( $context, $page, $language->getCode() );
1418 $mwService = MediaWikiServices::getInstance();
1420 $translationUrl = $mwService->getUrlUtils()->expand(
1421 $page->getTranslationUrl( $code ), PROTO_RELATIVE
1424 $msg = $context->msg(
'tpt-translation-intro',
1426 ':' . $page->getTitle()->getPrefixedText(),
1427 $language->formatNum( $per )
1431 $header = Html::rawElement(
1434 'class' =>
'mw-pt-translate-header noprint',
1435 'dir' => $language->getDir(),
1436 'lang' => $language->getHtmlCode(),
1441 $output->addHTML( $header );
1443 if ( $wgTranslateKeepOutdatedTranslations ) {
1444 $groupId = $page->getMessageGroupId();
1446 $stats = MessageGroupStats::forItem( $groupId, $code );
1447 if ( $stats[MessageGroupStats::FUZZY] ) {
1449 $wrap = Html::rawElement(
1452 'class' =>
'mw-pt-translate-header',
1453 'dir' => $language->getDir(),
1454 'lang' => $language->getHtmlCode()
1456 '<span class="mw-translate-fuzzy">$1</span>'
1459 $output->wrapWikiMsg( $wrap, [
'tpt-translation-intro-fuzzy' ] );
1464 private static function isAllowedContentModel( Content $content, PageReference $page ): bool {
1465 $config = MediaWikiServices::getInstance()->getMainConfig();
1466 $allowedModels = $config->get(
'PageTranslationAllowedContentModels' );
1467 $contentModel = $content->getModel();
1468 $allowed = (bool)( $allowedModels[$contentModel] ??
false );
1471 if ( $allowed && !$content instanceof TextContent ) {
1472 LoggerFactory::getInstance( LogNames::MAIN )->error(
1473 'Expected {title} to have content of type TextContent, got {contentType}. ' .
1474 '$wgPageTranslationAllowedContentModels is incorrectly configured with a non-text content model.',
1476 'title' => (
string)$page,
1477 'contentType' => get_class( $content )
1491 $movePageSpec = $list[
'Movepage'] ??
null;
1494 if ( $movePageSpec ===
null ) {
1498 $list[
'Movepage'] = [
1499 'class' => MoveTranslatableBundleSpecialPage::class,
1502 'Translate:TranslatableBundleMover',
1503 'Translate:TranslatableBundleFactory',
1521 if ( $action ===
'read' ) {
1525 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance( CACHE_ANYTHING );
1526 $key = $cache->makeKey(
'pt-lock', sha1( $title->getPrefixedText() ) );
1527 if ( $cache->get( $key ) ===
'locked' ) {
1528 $result = [
'pt-locked-page' ];
1544 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1546 $isTranslationPage = TranslatablePage::isTranslationPage( $out->getTitle() );
1547 if ( !$isTranslationPage
1548 && !TranslatablePage::isSourcePage( $out->getTitle() )
1554 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1556 $out->isArticle() &&
1557 $nsInfo->hasSubpages( $out->getTitle()->getNamespace() )
1559 $ptext = $out->getTitle()->getPrefixedText();
1560 $links = explode(
'/', $ptext );
1561 if ( count( $links ) > 1 ) {
1562 array_pop( $links );
1563 if ( $isTranslationPage ) {
1565 array_pop( $links );
1570 $sitedir = $skin->getLanguage()->getDir();
1572 foreach ( $links as $link ) {
1573 $growinglink .= $link;
1575 $linkObj = Title::newFromText( $growinglink );
1577 if ( $linkObj && $linkObj->isKnown() ) {
1578 $getlink = $linker->makeKnownLink(
1579 SpecialPage::getTitleFor(
'MyLanguage', $growinglink ),
1586 $subpages .= $skin->msg(
'pipe-separator' )->escaped();
1588 $subpages .=
'< ';
1591 $subpages .= Html::rawElement(
'bdi', [
'dir' => $sitedir ], $getlink );
1597 $growinglink .=
'/';
1615 $title = $skin->getTitle();
1617 $code = $handle->getCode();
1618 $user = $skin->getUser();
1620 if ( TranslatablePage::isSourcePage( $title ) && !TranslatablePage::newFromTitle( $title )->isBroken() ) {
1621 if ( $user->isAllowed(
'pagetranslation' ) ) {
1622 $tabs[
'actions'][
'marktranslation'] = [
1623 'text' => $skin->msg(
'translate-ca-marktranslation' )->text(),
1624 'href' => SpecialPage::getTitleFor(
'PageTranslation' )->getLocalURL( [
1625 'target' => $title->getPrefixedText(),
1633 $page = TranslatablePage::isTranslationPage( $title );
1635 if ( !$page || $page->getSourceLanguageCode() === $code ) {
1639 if ( isset( $tabs[
'views'][
'edit'] ) ) {
1641 $tabs[
'views'][
'edit'][
'text'] = $skin->msg(
'tpt-tab-translate' )->text();
1642 $tabs[
'views'][
'edit'][
'href'] = $page->getTranslationUrl( $code );
1643 } elseif ( $user->isAllowed(
'translate' ) ) {
1644 $mwInstance = MediaWikiServices::getInstance();
1645 $namespaceProtection = $mwInstance->getMainConfig()->get( MainConfigNames::NamespaceProtection );
1646 $permissionManager = $mwInstance->getPermissionManager();
1648 !$permissionManager->userHasAllRights(
1649 $user, ...(array)( $namespaceProtection[ NS_TRANSLATIONS ] ?? [] )
1656 'text' => $skin->msg(
'tpt-tab-translate' )->text(),
1657 'href' => $page->getTranslationUrl( $code ),
1661 $viewsourcePos = array_keys( array_keys( $tabs[
'views'] ),
'viewsource',
true )[0] ??
null;
1663 if ( $viewsourcePos !==
null ) {
1666 array_splice( $tabs[
'views'], $viewsourcePos, 1, [
'translate' => $tab ] );
1670 $tabs[
'views'][
'translate'] = $tab;
1688 LinkTarget $oldLinkTarget,
1689 LinkTarget $newLinkTarget,
1690 UserIdentity $userIdentity,
1694 RevisionRecord $revisionRecord
1696 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $userIdentity );
1701 if ( defined(
'MEDIAWIKI_JOB_RUNNER' ) && $user->equals( FuzzyBot::getUser() ) ) {
1705 $oldTitle = Title::newFromLinkTarget( $oldLinkTarget );
1706 $newTitle = Title::newFromLinkTarget( $newLinkTarget );
1708 foreach ( [ $oldTitle, $newTitle ] as $title ) {
1711 if ( !$handle->isValid() || $handle->isDoc() ) {
1715 $group = $handle->getGroup();
1720 $language = $handle->getCode();
1723 if ( $language ===
'' ) {
1729 if ( $group !== $groupLast ) {
1730 $groupLast = $group;
1731 $page = TranslatablePage::newFromTitle( $group->getTitle() );
1732 self::updateTranslationPage( $page, $language, $user, 0, $reason );
1755 $title = $unit->getTitle();
1757 if ( !$handle->isValid() ) {
1761 $group = $handle->getGroup();
1766 $mwServices = MediaWikiServices::getInstance();
1769 $mwServices->getJobQueueGroup()->push(
1770 RebuildMessageGroupStatsJob::newRefreshGroupsJob( [ $group->getId() ] )
1774 if ( self::$isDeleteTranslatableBundleJobRunning ) {
1778 $target = $group->getTitle();
1779 $langCode = $handle->getCode();
1780 $fname = __METHOD__;
1782 $dbw = $mwServices->getConnectionProvider()->getPrimaryDatabase();
1783 $callback =
function () use (
1792 $translationPageTitle = $target->getSubpage( $langCode );
1795 if ( !$translationPageTitle || !$translationPageTitle->exists( IDBAccessObject::READ_LATEST ) ) {
1799 $dbw->startAtomic( $fname );
1801 $page = TranslatablePage::newFromTitle( $target );
1803 if ( !$handle->isDoc() ) {
1804 $unitTitle = $handle->getTitle();
1806 self::updateTranslationPage(
1807 $page, $langCode, $user, 0, $reason, RenderTranslationPageJob::ACTION_DELETE, $unitTitle
1811 $dbw->endAtomic( $fname );
1814 $dbw->onTransactionCommitOrIdle( $callback, __METHOD__ );
1822 foreach ( $titles as $index => $title ) {
1824 if ( Utilities::isTranslationPage( $handle ) ) {
1825 unset( $titles[ $index ] );
1835 foreach ( $titles as $index => $title ) {
1838 TranslatablePage::isSourcePage( $title ) ||
1839 Utilities::isTranslationPage( $handle )
1841 unset( $titles[ $index ] );
1846 public static function getSpecialManageMessageGroupSubscriptionsLink(
1851 'pagelink' => SpecialPage::getTitleFor(
'ManageMessageGroupSubscriptions' )->getPrefixedText()
1861 if ( !Utilities::isTranslationPage( $handle ) ) {
1864 $code = $handle->getCode();
1865 $categories = $linksUpdate->getParserOutput()->getCategoryNames();
1866 $editSummary = wfMessage(
1867 'translate-category-summary',
1868 $linksUpdate->getTitle()->getPrefixedText()
1869 )->inContentLanguage()->text();
1870 foreach ( $categories as $category ) {
1871 $categoryTitle = Title::makeTitle( NS_CATEGORY, $category );
1876 $categoryTranslationPage = TranslatablePage::isTranslationPage( $categoryTitle );
1878 $categoryTranslationPage
1879 && $categoryHandle->getCode() == $code
1880 && !$categoryTitle->exists()
1882 self::updateTranslationPage(
1883 $categoryTranslationPage,
1885 FuzzyBot::getUser(),
1888 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'));}, 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), $services->getContentLanguageCode() ->toString(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance(LogNames::GROUP_SUBSCRIPTION), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):?MessageGroupSubscriptionHookHandler { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getConnectionProvider(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getConnectionProvider());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getConnectionProvider());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup(), $services->getNamespaceInfo(), $services->getTitleFactory(), $services->getFormatterFactory());}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory(), $services->get( 'Translate:HookRunner'),);}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getConnectionProvider());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@phpcs-require-sorted-array
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.
Wraps the translatable page sections into a message group.
Interface for message groups.