5namespace MediaWiki\Extension\Translate\PageTranslation;
10use MediaWiki\Category\Category;
11use MediaWiki\CommentStore\CommentStoreComment;
12use MediaWiki\Config\Config;
13use MediaWiki\Content\Content;
14use MediaWiki\Content\TextContent;
15use MediaWiki\Context\IContextSource;
16use MediaWiki\Context\RequestContext;
17use MediaWiki\Deferred\DeferredUpdates;
18use MediaWiki\Deferred\LinksUpdate\LinksUpdate;
27use MediaWiki\Html\Html;
28use MediaWiki\Language\Language;
29use MediaWiki\Language\LanguageCode;
30use MediaWiki\Languages\LanguageNameUtils;
31use MediaWiki\Linker\LinkTarget;
32use MediaWiki\Logger\LoggerFactory;
33use MediaWiki\MainConfigNames;
34use MediaWiki\MediaWikiServices;
35use MediaWiki\Output\OutputPage;
36use MediaWiki\Page\PageIdentity;
37use MediaWiki\Page\PageReference;
38use MediaWiki\Parser\Parser;
39use MediaWiki\Parser\ParserOutput;
40use MediaWiki\Parser\ParserOutputFlags;
41use MediaWiki\Parser\PPFrame;
42use MediaWiki\ResourceLoader\Context;
43use MediaWiki\Revision\MutableRevisionRecord;
44use MediaWiki\Revision\RenderedRevision;
45use MediaWiki\Revision\RevisionRecord;
46use MediaWiki\Revision\SlotRecord;
47use MediaWiki\SpecialPage\SpecialPage;
48use MediaWiki\Status\Status;
49use MediaWiki\Storage\EditResult;
50use MediaWiki\StubObject\StubUserLang;
51use MediaWiki\Title\Title;
52use MediaWiki\User\User;
53use MediaWiki\User\UserIdentity;
57use Wikimedia\Rdbms\IDBAccessObject;
58use Wikimedia\ScopedCallback;
69 private const PAGEPROP_HAS_LANGUAGES_TAG =
'translate-has-languages-tag';
71 public static $allowTargetEdit =
false;
75 public static $renderingContext =
false;
77 private static $languageLinkData = [];
86 public static function renderTagPage( $wikitextParser, &$text, $state ): void {
87 if ( $text === null ) {
95 if ( $wikitextParser->getOptions()->getInterfaceMessage() ) {
101 if ( $wikitextParser->getOptions()->getIsSectionPreview() ) {
102 $translatablePageParser = Services::getInstance()->getTranslatablePageParser();
103 $text = $translatablePageParser->cleanupTags( $text );
107 $title = MediaWikiServices::getInstance()
109 ->castFromPageReference( $wikitextParser->getPage() );
119 $wikitextParser->getOutput()->setUnsortedPageProperty(
'translate-is-translation' );
122 self::$renderingContext =
true;
124 $name = $page->getPageDisplayTitle( $code );
126 $name = $wikitextParser->recursivePreprocess( $name );
128 $langConv = MediaWikiServices::getInstance()->getLanguageConverterFactory()
129 ->getLanguageConverter( $wikitextParser->getTargetLanguage() );
130 $name = $langConv->convert( $name );
131 $wikitextParser->getOutput()->setDisplayTitle( $name );
133 self::$renderingContext =
false;
134 }
catch ( Exception $e ) {
136 'T302754 Failed to set display title for page {title}',
138 'title' => $title->getPrefixedDBkey(),
140 'pageid' => $title->getId(),
149 'languagecode' => $code,
150 'messagegroupid' => $page->getMessageGroupId(),
151 'sourcepagetitle' => [
152 'namespace' => $page->getTitle()->getNamespace(),
153 'dbkey' => $page->getTitle()->getDBkey()
157 $wikitextParser->getOutput()->setExtensionData(
'translate-translation-page', $extensionData );
159 $wikitextParser->getOutput()->setOutputFlag( ParserOutputFlags::NO_SECTION_EDIT_LINKS );
170 $translatablePageParser =
Services::getInstance()->getTranslatablePageParser();
172 if ( $translatablePageParser->containsMarkup( $text ) ) {
174 $parserOutput = $translatablePageParser->parse( $text );
176 $text = $parserOutput->sourcePageTextForRendering(
177 $wikitextParser->getTargetLanguage()
180 wfDebug(
'ParsingFailure caught; expected' );
186 $text = $unit->getTextForTrans();
202 ?LinkTarget $contextLink,
203 ?LinkTarget $templateLink,
205 ?RevisionRecord &$revRecord
207 if ( !$templateLink ) {
211 $templateTitle = Title::newFromLinkTarget( $templateLink );
213 $templateTranslationPage = TranslatablePage::isTranslationPage( $templateTitle );
214 if ( $templateTranslationPage ) {
217 $revRecord = $templateTranslationPage->getRevisionRecordWithFallback();
220 LoggerFactory::getInstance( LogNames::MAIN )->warning(
221 "T323863: Could not fetch any revision record for '{groupid}'",
222 [
'groupid' => $templateTranslationPage->getMessageGroupId() ]
228 if ( !TranslatablePage::isSourcePage( $templateTitle ) ) {
232 $translatableTemplatePage = TranslatablePage::newFromTitle( $templateTitle );
234 if ( !( $translatableTemplatePage->supportsTransclusion() ??
false ) ) {
239 $store = MediaWikiServices::getInstance()->getRevisionStore();
241 if ( $contextLink ) {
243 $templateTranslationTitle = $templateTitle->getSubpage(
244 Title::newFromLinkTarget( $contextLink )->getPageLanguage()->getCode()
247 if ( $templateTranslationTitle ) {
248 if ( $templateTranslationTitle->exists() ) {
250 $revRecord = $store->getRevisionByTitle( $templateTranslationTitle );
255 $revRecord =
new MutableRevisionRecord( $templateTranslationTitle );
263 $sourceTemplateTitle = $templateTitle->getSubpage(
264 $translatableTemplatePage->getMessageGroup()->getSourceLanguage()
266 if ( $sourceTemplateTitle && $sourceTemplateTitle->exists() ) {
267 $revRecord = $store->getRevisionByTitle( $sourceTemplateTitle );
280 if ( TranslatablePage::isTranslationPage( $title ) ) {
281 [ , $code ] = Utilities::figureMessage( $title->getText() );
282 $pageLang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $code );
294 if ( TranslatablePage::isSourcePage( $title ) ) {
295 $msg = wfMessage(
'translate-edit-tag-warning' )->inContentLanguage();
296 if ( !$msg->isDisabled() ) {
297 $notices[
'translate-tag'] = $msg->parseAsBlock();
300 $notices[] = Html::warningBox(
301 wfMessage(
'tps-edit-sourcepage-text' )->parse(),
302 'translate-edit-documentation'
307 $request = RequestContext::getMain()->getRequest();
308 if ( $request->getVal(
'action' ) ===
'visualeditor' &&
309 $request->getVal(
'paction' ) !==
'wikitext'
311 $notices[] = Html::warningBox(
312 wfMessage(
'tps-edit-sourcepage-ve-warning-limited-text' )->parse(),
313 'translate-edit-documentation'
325 global $wgTranslatePageTranslationULS;
327 $title = $out->getTitle();
328 $isSource = TranslatablePage::isSourcePage( $title );
329 $isTranslation = TranslatablePage::isTranslationPage( $title );
331 if ( $isSource || $isTranslation ) {
332 if ( $wgTranslatePageTranslationULS ) {
333 $out->addModules(
'ext.translate.pagetranslation.uls' );
338 $out->addModuleStyles(
'ext.translate.edit.documentation.styles' );
341 $out->addModuleStyles(
'ext.translate' );
343 $out->addJsConfigVars(
'wgTranslatePageTranslation', $isTranslation ?
'translation' :
'source' );
354 return !TranslatablePage::isTranslationPage( $out->getTitle() );
370 TextContent $content,
377 if ( $user->equals( FuzzyBot::getUser() ) ) {
387 $page = TranslatablePage::newFromTitle( $group->getTitle() );
390 if ( !$handle->
isDoc() ) {
392 DeferredUpdates::addCallableUpdate(
393 function () use ( $page, $code, $user, $flags, $summary, $handle ) {
395 self::updateTranslationPage( $page, $code, $user, $flags, $summary,
null, $unitTitle );
401 private static function updateTranslationPage(
407 ?
string $triggerAction =
null,
408 ?Title $unitTitle =
null
410 $source = $page->getTitle();
411 $target = $source->getSubpage( $code );
412 $mwInstance = MediaWikiServices::getInstance();
415 $flags &= ~EDIT_NEW & ~EDIT_UPDATE;
418 $unitTitleText = $unitTitle ? $unitTitle->getPrefixedText() :
null;
419 $job = RenderTranslationPageJob::newJob( $target, $triggerAction, $unitTitleText );
421 if ( !$user->equals( FuzzyBot::getUser() ) ) {
422 $session = RequestContext::getMain()->exportSession();
424 $job->setUser( $user, $session );
425 $job->setSummary( $summary );
426 $job->setFlags( $flags );
427 $mwInstance->getJobQueueGroup()->push( $job );
431 $wikiPageFactory = $mwInstance->getWikiPageFactory();
432 foreach ( $pages as $title ) {
433 if ( $title->equals( $target ) ) {
438 $wikiPage = $wikiPageFactory->newFromTitle( $title );
439 $wikiPage->doPurge();
441 $sourceWikiPage = $wikiPageFactory->newFromTitle( $source );
442 $sourceWikiPage->doPurge();
450 $variableIDs[] =
'translatablepage';
458 array &$variableCache,
463 switch ( $magicWordId ) {
464 case 'translatablepage':
465 $pageStatus = self::getTranslatablePageStatus( $parser->getPage() );
466 $ret = $pageStatus !==
null ? $pageStatus[
'page']->getTitle()->getPrefixedText() :
'';
467 $variableCache[$magicWordId] = $ret;
478 public static function languages( $data, $params, $parser ) {
479 global $wgPageTranslationLanguageList;
481 if ( $wgPageTranslationLanguageList ===
'sidebar-only' ) {
485 self::$renderingContext =
true;
486 $context =
new ScopedCallback(
static function () {
487 self::$renderingContext =
false;
492 $parser->getOutput()->setUnsortedPageProperty( self::PAGEPROP_HAS_LANGUAGES_TAG );
494 $currentPage = $parser->getPage();
495 $pageStatus = self::getTranslatablePageStatus( $currentPage );
496 if ( !$pageStatus ) {
500 $page = $pageStatus[
'page' ];
501 $status = $pageStatus[
'languages' ];
508 $userLang = $parser->getOptions()->getUserLangObj();
509 $userLangCode = $userLang->getCode();
513 $sourceLanguage = $pageTitle->getPageLanguage()->getCode();
516 $langFactory = MediaWikiServices::getInstance()->getLanguageFactory();
517 foreach ( $status as $code => $percent ) {
519 $name = Utilities::getLanguageName( $code, LanguageNameUtils::AUTONYMS );
522 $suffix = ( $code === $sourceLanguage ) ?
'' :
"/$code";
523 $targetTitleString = $pageTitle->getDBkey() . $suffix;
524 $subpage = Title::makeTitle( $pageTitle->getNamespace(), $targetTitleString );
527 if ( $code === $userLangCode ) {
528 $classes[] =
'mw-pt-languages-ui';
531 $linker = $parser->getLinkRenderer();
532 $lang = $langFactory->getLanguage( $code );
533 if ( $currentPage->isSamePageAs( $subpage ) ) {
534 $classes[] =
'mw-pt-languages-selected';
535 $classes = array_merge( $classes, self::tpProgressIcon( (
float)$percent ) );
538 'lang' => $lang->getHtmlCode(),
539 'dir' => $lang->getDir(),
542 $contents = Html::element(
'span', $attribs, $name );
543 } elseif ( $subpage->isKnown() ) {
545 if ( !is_string( $pagename ) ) {
546 $pagename = $subpage->getPrefixedText();
549 $classes = array_merge( $classes, self::tpProgressIcon( (
float)$percent ) );
551 $title = wfMessage(
'tpt-languages-nonzero' )
552 ->page( $parser->getPage() )
553 ->inLanguage( $userLang )
554 ->params( $pagename )
555 ->numParams( 100 * $percent )
560 'lang' => $lang->getHtmlCode(),
561 'dir' => $lang->getDir(),
564 $contents = $linker->makeKnownLink( $subpage, $name, $attribs );
569 $specialTranslateTitle = SpecialPage::getTitleFor(
'Translate' );
579 'title' => wfMessage(
'tpt-languages-zero' )
580 ->page( $parser->getPage() )
581 ->inLanguage( $userLang )
584 'lang' => $lang->getHtmlCode(),
585 'dir' => $lang->getDir(),
587 $contents = $linker->makeKnownLink( $specialTranslateTitle, $name, $attribs, $params );
589 $languages[ $name ] = Html::rawElement(
'li', [], $contents );
594 $languages = array_values( $languages );
595 $languages = implode(
"\n", $languages );
597 $out = Html::openElement(
'div', [
598 'class' =>
'mw-pt-languages noprint navigation-not-searchable',
599 'lang' => $userLang->getHtmlCode(),
600 'dir' => $userLang->getDir()
602 $out .= Html::rawElement(
'div', [
'class' =>
'mw-pt-languages-label' ],
603 wfMessage(
'tpt-languages-legend' )
604 ->page( $parser->getPage() )
605 ->inLanguage( $userLang )
608 $out .= Html::rawElement(
610 [
'class' =>
'mw-pt-languages-list' ],
613 $out .= Html::closeElement(
'div' );
615 $parser->getOutput()->addModuleStyles( [
616 'ext.translate.tag.languages',
628 private static function tpProgressIcon(
float $percent ) {
629 $classes = [
'mw-pt-progress' ];
631 if ( $percent < 15 ) {
632 $classes[] =
'mw-pt-progress--low';
633 } elseif ( $percent < 70 ) {
634 $classes[] =
'mw-pt-progress--med';
635 } elseif ( $percent < 100 ) {
636 $classes[] =
'mw-pt-progress--high';
638 $classes[] =
'mw-pt-progress--complete';
647 private static function getTranslatablePageStatus( ?PageReference $pageReference ): ?array {
648 if ( $pageReference === null ) {
651 $title = Title::newFromPageReference( $pageReference );
653 $page = TranslatablePage::newFromTitle( $title );
655 $page = TranslatablePage::isTranslationPage( $title );
658 if ( $page ===
false || $page->
getMarkedTag() ===
null ) {
662 $status = $page->getTranslationPercentages();
667 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
669 $priorityLanguages = $messageGroupMetadata->get( $page->
getMessageGroupId(),
'prioritylangs' );
670 if ( $priorityLanguages !==
false && $priorityLanguages !==
'' ) {
671 $status += array_fill_keys( explode(
',', $priorityLanguages ), 0 );
676 'languages' => $status
686 global $wgPageTranslationLanguageList;
688 if ( $wgPageTranslationLanguageList ===
'tag-only' ) {
692 if ( $wgPageTranslationLanguageList ===
'sidebar-fallback' ) {
693 $pageProps = MediaWikiServices::getInstance()->getPageProps();
694 $languageProp = $pageProps->getProperties( $title, self::PAGEPROP_HAS_LANGUAGES_TAG );
695 if ( $languageProp !== [] ) {
702 $status = self::getTranslatablePageStatus( $title );
707 self::$renderingContext =
true;
708 $context =
new ScopedCallback(
static function () {
709 self::$renderingContext =
false;
712 $page = $status[
'page' ];
713 $languages = $status[
'languages' ];
714 $mwServices = MediaWikiServices::getInstance();
715 $en = $mwServices->getLanguageFactory()->getLanguage(
'en' );
718 $lb = $mwServices->getLinkBatchFactory()->newLinkBatch();
719 foreach ( array_keys( $languages ) as $code ) {
720 $title = $page->getTitle()->getSubpage( $code );
721 $lb->addObj( $title );
724 $languageNameUtils = $mwServices->getLanguageNameUtils();
725 foreach ( $languages as $code => $percentage ) {
726 $title = $page->getTitle()->getSubpage( $code );
727 $placeholderValue =
"$code-x-pagetranslation:{$title->getPrefixedText()}";
728 $translatedName = $page->getPageDisplayTitle( $code ) ?: $title->getPrefixedText();
730 if ( $title->exists() ) {
731 $href = $title->getLocalURL();
732 $classes = self::tpProgressIcon( (
float)$percentage );
733 $titleAttribute = wfMessage(
'tpt-languages-nonzero' )
734 ->params( $translatedName )
735 ->numParams( 100 * $percentage );
737 $href = SpecialPage::getTitleFor(
'Translate' )->getLocalURL( [
738 'group' => $page->getMessageGroupId(),
741 $classes = [
'mw-pt-progress--none' ];
742 $titleAttribute = wfMessage(
'tpt-languages-zero' );
745 self::$languageLinkData[ $placeholderValue ] = [
748 'classes' => $classes,
749 'autonym' => $en->ucfirst( $languageNameUtils->getLanguageName( $code ) ),
750 'title' => $titleAttribute,
754 $languageLinks[] = $placeholderValue;
771 $data = self::$languageLinkData[$link[
'text']] ??
null;
776 $link[
'class'] .=
' interwiki-pagetranslation ' . implode(
' ', $data[
'classes'] );
777 $link[
'href'] = $data[
'href'];
778 $link[
'text'] = $data[
'autonym'];
779 $link[
'title'] = $data[
'title']->inLanguage( $out->getLanguage()->getCode() )->text();
780 $link[
'lang'] = LanguageCode::bcp47( $data[
'language'] );
781 $link[
'hreflang'] = LanguageCode::bcp47( $data[
'language'] );
783 $out->addModuleStyles(
'ext.translate.tag.languages' );
801 $syntaxErrorStatus = self::tpSyntaxError( $context->getTitle(), $content );
803 if ( $syntaxErrorStatus ) {
804 $status->merge( $syntaxErrorStatus );
805 return $syntaxErrorStatus->isGood();
811 private static function tpSyntaxError( ?PageIdentity $page, ?Content $content ): ?Status {
812 if ( !$page || !self::isAllowedContentModel( $content, $page ) ) {
816 '@phan-var TextContent $content';
817 $text = $content->getText();
820 $text = TextContent::normalizeLineEndings( $text );
821 $status = Status::newGood();
822 $parser = Services::getInstance()->getTranslatablePageParser();
823 if ( $parser->containsMarkup( $text ) ) {
825 $parser->parse( $text );
826 }
catch ( ParsingFailure $e ) {
827 $status->fatal( $e->getMessageSpecification() );
846 RenderedRevision $renderedRevision,
848 CommentStoreComment $summary,
852 $content = $renderedRevision->getRevision()->getContent( SlotRecord::MAIN );
854 $status = self::tpSyntaxError(
855 $renderedRevision->getRevision()->getPage(),
860 $hookStatus->merge( $status );
861 return $status->isGood();
879 UserIdentity $userIdentity,
882 RevisionRecord $revisionRecord,
883 EditResult $editResult
885 $content = $wikiPage->getContent();
888 if ( !self::isAllowedContentModel( $content, $wikiPage ) ) {
892 '@phan-var TextContent $content';
893 $text = $content->getText();
895 $parser = Services::getInstance()->getTranslatablePageParser();
896 if ( $parser->containsMarkup( $text ) ) {
898 $page = TranslatablePage::newFromTitle( $wikiPage->getTitle() );
903 $tpStatusUpdater = Services::getInstance()->getTranslatablePageStore();
904 $tpStatusUpdater->performStatusUpdate( $wikiPage->getTitle() );
923 $parentId = $rev->getParentId();
924 if ( $parentId === 0 || $parentId ===
null ) {
929 $prevRev = MediaWikiServices::getInstance()
930 ->getRevisionLookup()
931 ->getRevisionById( $parentId );
933 if ( !$prevRev || !$rev->hasSameContent( $prevRev ) ) {
938 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
939 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
940 $bundle = $bundleFactory->getBundle( $title );
943 $bundleStore = $bundleFactory->getStore( $bundle );
944 $bundleStore->handleNullRevisionInsert( $bundle, $rev );
970 if ( !$handle->isPageTranslation() || $action ===
'read' ) {
977 if ( $handle->isValid() ) {
978 $group = $handle->getGroup();
979 $groupId = $group->getId();
980 $permissionTitleCheck =
null;
983 $permissionTitleCheck = $group->getTitle();
987 $permissionTitleCheck = Title::newFromID( $group->getBundlePageId() );
990 if ( $permissionTitleCheck ) {
991 if ( $handle->getCode() === $group->getSourceLanguage() && !$user->equals( FuzzyBot::getUser() ) ) {
996 $allowedActionList = [
997 'read',
'deletedtext',
'deletedhistory',
998 'deleterevision',
'suppressrevision',
'viewsuppressed',
1002 if ( !in_array( $action, $allowedActionList ) ) {
1003 $result = [
'tpt-cant-edit-source-language', $permissionTitleCheck ];
1008 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1009 if ( $permissionManager->isBlockedFrom( $user, $permissionTitleCheck ) ) {
1010 $block = $user->getBlock();
1012 $error =
new UserBlockedError( $block, $user );
1013 $errorMessage = $error->getMessageObject();
1014 $result = array_merge( [ $errorMessage->getKey() ], $errorMessage->getParams() );
1018 if ( $action ===
'create' && $permissionTitleCheck->inNamespace( NS_CATEGORY ) ) {
1019 $renderedPage = $permissionTitleCheck->getSubpage( $handle->getCode() );
1020 if ( !$renderedPage->exists() ) {
1021 $cat = Category::newFromTitle( $renderedPage );
1022 if ( $cat->getMemberCount() === 0 ) {
1023 if ( !$permissionManager->userCan(
'translate-empty-category', $user, $renderedPage ) ) {
1024 $result = [
'tpt-create-empty-category', $renderedPage ];
1035 if ( $action !==
'create' ) {
1039 if ( !$handle->isValid() ) {
1045 $translatablePage = self::checkTranslatablePageSlow( $title );
1046 MediaWikiServices::getInstance()->getStatsFactory()
1047 ->withComponent(
'Translate' )
1048 ->getCounter(
'slow_translatable_page_check' )
1049 ->setLabel(
'valid', $translatablePage ?
'yes' :
'no' )
1052 if ( $translatablePage ) {
1053 $groupId = $translatablePage->getMessageGroupId();
1060 $error = self::getTranslationRestrictions( $handle, $groupId );
1061 $result = $error ?: $result;
1062 return $error === [];
1066 LoggerFactory::getInstance( LogNames::MAIN )->info(
1067 'Unknown translation page: {title}',
1068 [
'title' => $title->getPrefixedDBkey() ]
1070 $result = [
'tpt-unknown-page' ];
1074 private static function checkTranslatablePageSlow( LinkTarget $unit ): ?
TranslatablePage {
1076 $translationPageTitle = Title::newFromText(
1077 $parts[
'sourcepage' ] .
'/' . $parts[
'language' ]
1079 if ( !$translationPageTitle ) {
1083 $translatablePage = TranslatablePage::isTranslationPage( $translationPageTitle );
1084 if ( !$translatablePage ) {
1088 $factory = Services::getInstance()->getTranslationUnitStoreFactory();
1089 $store = $factory->getReader( $translatablePage->getTitle() );
1090 $units = $store->getNames();
1092 if ( !in_array( $parts[
'section' ], $units ) ) {
1096 return $translatablePage;
1106 private static function getTranslationRestrictions( MessageHandle $handle, $groupId ) {
1107 global $wgTranslateDocumentationLanguageCode;
1110 if ( $handle->getCode() === $wgTranslateDocumentationLanguageCode ) {
1114 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
1116 $force = $messageGroupMetadata->get( $groupId,
'priorityforce' );
1117 if ( $force !==
'on' ) {
1122 $languages = $messageGroupMetadata->get( $groupId,
'prioritylangs' );
1123 $reason = $messageGroupMetadata->get( $groupId,
'priorityreason' );
1124 if ( !$languages ) {
1126 return [
'tpt-translation-restricted-no-priority-languages', $reason ];
1128 return [
'tpt-translation-restricted-no-priority-languages-no-reason' ];
1131 $filter = array_flip( explode(
',', $languages ) );
1132 if ( !isset( $filter[$handle->getCode()] ) ) {
1134 return [
'tpt-translation-restricted', $reason ];
1137 return [
'tpt-translation-restricted-no-reason' ];
1153 if ( self::$allowTargetEdit ) {
1158 'read',
'deletedtext',
'deletedhistory',
1159 'deleterevision',
'suppressrevision',
'viewsuppressed',
1162 'translate-empty-category'
1164 $needsPageTranslationRight = in_array( $action, [
'delete',
'undelete' ] );
1165 if ( in_array( $action, $inclusionList ) ||
1166 ( $needsPageTranslationRight && $user->isAllowed(
'pagetranslation' ) )
1171 $page = TranslatablePage::isTranslationPage( $title );
1173 $mwService = MediaWikiServices::getInstance();
1174 if ( $needsPageTranslationRight ) {
1175 $context = RequestContext::getMain();
1176 $statusFormatter = $mwService->getFormatterFactory()->getStatusFormatter( $context );
1177 $permissionError = $mwService->getPermissionManager()
1178 ->newFatalPermissionDeniedStatus(
'pagetranslation', $context );
1179 $result = $statusFormatter->getMessage( $permissionError );
1183 [ , $code ] = Utilities::figureMessage( $title->getText() );
1187 'tpt-source-mirror',
1188 ':' . $page->
getTitle()->getPrefixedText()
1193 $translationUrl = $mwService->getUrlUtils()->expand(
1199 ':' . $page->
getTitle()->getPrefixedText(),
1214 public static function preventMoves( Title $oldTitle, Title $newTitle, StatusValue $status ) {
1215 if ( self::$allowTargetEdit ) {
1219 if ( TranslatablePage::newFromTitle( $oldTitle )->getMarkedTag() !==
null ) {
1220 $status->fatal(
'tpt-manual-move-source', $oldTitle->getPrefixedText() );
1222 $tp = TranslatablePage::isTranslationPage( $oldTitle );
1225 $status->fatal(
'tpt-manual-move-translation', $tp->getTitle() );
1240 $title = $article->getTitle();
1241 $bundle = Services::getInstance()->getTranslatableBundleFactory()->getBundle( $title );
1242 $isDeletableBundle = $bundle && $bundle->isDeletable();
1243 if ( $isDeletableBundle || TranslatablePage::isTranslationPage( $title ) ) {
1244 $new = SpecialPage::getTitleFor(
1245 'PageTranslationDeletePage',
1246 $title->getPrefixedText()
1248 $out->redirect( $new->getFullURL() );
1262 if ( $article->getOldID() ) {
1266 $articleTitle = $article->getTitle();
1267 $transPage = TranslatablePage::isTranslationPage( $articleTitle );
1268 $context = $article->getContext();
1270 self::translationPageHeader( $context, $transPage );
1272 $viewTranslatablePage = Services::getInstance()->getTranslatablePageView();
1273 $user = $context->getUser();
1274 if ( $viewTranslatablePage->canDisplayTranslationSettingsBanner( $articleTitle, $user ) ) {
1275 $output = $context->getOutput();
1276 $pageUrl = SpecialPage::getTitleFor(
'PageTranslation' )->getFullURL( [
1278 'target' => $articleTitle->getPrefixedDBkey(),
1282 $context->msg(
'pt-cta-mark-translation', $pageUrl )->parse(),
1283 'translate-cta-pt-mark'
1287 self::sourcePageHeader( $context );
1292 private static function sourcePageHeader( IContextSource $context ) {
1293 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1295 $language = $context->getLanguage();
1296 $title = $context->getTitle();
1298 $page = TranslatablePage::newFromTitle( $title );
1302 $latest = $title->getLatestRevID();
1305 if ( $marked && $context->getUser()->isAllowed(
'translate' ) ) {
1306 $actions[] = self::getTranslateLink( $context, $page,
null );
1309 $hasChanges = $ready === $latest && $marked !== $latest;
1310 if ( $hasChanges ) {
1311 $diffUrl = $title->getFullURL( [
'oldid' => $marked,
'diff' => $latest ] );
1313 if ( $context->getUser()->isAllowed(
'pagetranslation' ) ) {
1314 $pageTranslation = SpecialPage::getTitleFor(
'PageTranslation' );
1315 $params = [
'target' => $title->getPrefixedText(),
'do' =>
'mark' ];
1317 if ( $marked ===
null ) {
1319 $linkDesc = $context->msg(
'translate-tag-markthis' )->text();
1320 $actions[] = $linker->makeKnownLink( $pageTranslation, $linkDesc, [], $params );
1322 $markUrl = $pageTranslation->getFullURL( $params );
1323 $actions[] = $context->msg(
'translate-tag-markthisagain', $diffUrl, $markUrl )
1327 $actions[] = $context->msg(
'translate-tag-hasnew', $diffUrl )->parse();
1331 if ( !count( $actions ) ) {
1335 $header = Html::rawElement(
1338 'class' =>
'mw-pt-translate-header noprint nomobile',
1339 'dir' => $language->getDir(),
1340 'lang' => $language->getHtmlCode(),
1342 $language->semicolonList( $actions )
1345 $context->getOutput()->addHTML( $header );
1348 private static function getTranslateLink(
1349 IContextSource $context,
1350 TranslatablePage $page,
1353 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1355 return $linker->makeKnownLink(
1356 SpecialPage::getTitleFor(
'Translate' ),
1357 $context->msg(
'translate-tag-translate-link-desc' )->text(),
1360 'group' => $page->getMessageGroupId(),
1361 'language' => $langCode,
1364 'action_source' =>
'translate_page'
1369 private static function translationPageHeader( IContextSource $context, TranslatablePage $page ) {
1370 global $wgTranslateKeepOutdatedTranslations;
1372 $title = $context->getTitle();
1373 if ( !$title->exists() ) {
1377 [ , $code ] = Utilities::figureMessage( $title->getText() );
1380 $pers = $page->getTranslationPercentages();
1382 if ( isset( $pers[$code] ) ) {
1383 $per = $pers[$code] * 100;
1386 $language = $context->getLanguage();
1387 $output = $context->getOutput();
1389 if ( $page->getSourceLanguageCode() === $code ) {
1391 $msg = self::getTranslateLink( $context, $page, $language->getCode() );
1393 $mwService = MediaWikiServices::getInstance();
1395 $translationUrl = $mwService->getUrlUtils()->expand(
1396 $page->getTranslationUrl( $code ), PROTO_RELATIVE
1399 $msg = $context->msg(
'tpt-translation-intro',
1401 ':' . $page->getTitle()->getPrefixedText(),
1402 $language->formatNum( $per )
1406 $header = Html::rawElement(
1409 'class' =>
'mw-pt-translate-header noprint',
1410 'dir' => $language->getDir(),
1411 'lang' => $language->getHtmlCode(),
1416 $output->addHTML( $header );
1418 if ( $wgTranslateKeepOutdatedTranslations ) {
1419 $groupId = $page->getMessageGroupId();
1421 $stats = MessageGroupStats::forItem( $groupId, $code );
1422 if ( $stats[MessageGroupStats::FUZZY] ) {
1424 $wrap = Html::rawElement(
1427 'class' =>
'mw-pt-translate-header',
1428 'dir' => $language->getDir(),
1429 'lang' => $language->getHtmlCode()
1431 '<span class="mw-translate-fuzzy">$1</span>'
1434 $output->wrapWikiMsg( $wrap, [
'tpt-translation-intro-fuzzy' ] );
1439 private static function isAllowedContentModel( Content $content, PageReference $page ): bool {
1440 $config = MediaWikiServices::getInstance()->getMainConfig();
1441 $allowedModels = $config->get(
'PageTranslationAllowedContentModels' );
1442 $contentModel = $content->getModel();
1443 $allowed = (bool)( $allowedModels[$contentModel] ??
false );
1446 if ( $allowed && !$content instanceof TextContent ) {
1447 LoggerFactory::getInstance( LogNames::MAIN )->error(
1448 'Expected {title} to have content of type TextContent, got {contentType}. ' .
1449 '$wgPageTranslationAllowedContentModels is incorrectly configured with a non-text content model.',
1451 'title' => (
string)$page,
1452 'contentType' => get_class( $content )
1466 $movePageSpec = $list[
'Movepage'] ??
null;
1469 if ( $movePageSpec ===
null ) {
1473 $list[
'Movepage'] = [
1474 'class' => MoveTranslatableBundleSpecialPage::class,
1477 'PermissionManager',
1478 'Translate:TranslatableBundleMover',
1479 'Translate:TranslatableBundleFactory',
1497 if ( $action ===
'read' ) {
1501 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance( CACHE_ANYTHING );
1502 $key = $cache->makeKey(
'pt-lock', sha1( $title->getPrefixedText() ) );
1503 if ( $cache->get( $key ) ===
'locked' ) {
1504 $result = [
'pt-locked-page' ];
1520 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1522 $isTranslationPage = TranslatablePage::isTranslationPage( $out->getTitle() );
1523 if ( !$isTranslationPage
1524 && !TranslatablePage::isSourcePage( $out->getTitle() )
1530 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1532 $out->isArticle() &&
1533 $nsInfo->hasSubpages( $out->getTitle()->getNamespace() )
1535 $ptext = $out->getTitle()->getPrefixedText();
1536 $links = explode(
'/', $ptext );
1537 if ( count( $links ) > 1 ) {
1538 array_pop( $links );
1539 if ( $isTranslationPage ) {
1541 array_pop( $links );
1546 $sitedir = $skin->getLanguage()->getDir();
1548 foreach ( $links as $link ) {
1549 $growinglink .= $link;
1551 $linkObj = Title::newFromText( $growinglink );
1553 if ( $linkObj && $linkObj->isKnown() ) {
1554 $getlink = $linker->makeKnownLink(
1555 SpecialPage::getTitleFor(
'MyLanguage', $growinglink ),
1562 $subpages .= $skin->msg(
'pipe-separator' )->escaped();
1564 $subpages .=
'< ';
1567 $subpages .= Html::rawElement(
'bdi', [
'dir' => $sitedir ], $getlink );
1573 $growinglink .=
'/';
1591 $title = $skin->getTitle();
1593 $code = $handle->getCode();
1594 $user = $skin->getUser();
1596 if ( TranslatablePage::isSourcePage( $title ) ) {
1597 if ( $user->isAllowed(
'pagetranslation' ) ) {
1598 $tabs[
'actions'][
'marktranslation'] = [
1599 'text' => $skin->msg(
'translate-ca-marktranslation' )->text(),
1600 'href' => SpecialPage::getTitleFor(
'PageTranslation' )->getLocalURL( [
1601 'target' => $title->getPrefixedText(),
1609 $page = TranslatablePage::isTranslationPage( $title );
1611 if ( !$page || $page->getSourceLanguageCode() === $code ) {
1615 if ( isset( $tabs[
'views'][
'edit'] ) ) {
1617 $tabs[
'views'][
'edit'][
'text'] = $skin->msg(
'tpt-tab-translate' )->text();
1618 $tabs[
'views'][
'edit'][
'href'] = $page->getTranslationUrl( $code );
1619 } elseif ( $user->isAllowed(
'translate' ) ) {
1620 $mwInstance = MediaWikiServices::getInstance();
1621 $namespaceProtection = $mwInstance->getMainConfig()->get( MainConfigNames::NamespaceProtection );
1622 $permissionManager = $mwInstance->getPermissionManager();
1624 !$permissionManager->userHasAllRights(
1625 $user, ...(array)( $namespaceProtection[ NS_TRANSLATIONS ] ?? [] )
1632 'text' => $skin->msg(
'tpt-tab-translate' )->text(),
1633 'href' => $page->getTranslationUrl( $code ),
1637 $viewsourcePos = array_keys( array_keys( $tabs[
'views'] ),
'viewsource',
true )[0] ??
null;
1639 if ( $viewsourcePos !==
null ) {
1642 array_splice( $tabs[
'views'], $viewsourcePos, 1, [
'translate' => $tab ] );
1646 $tabs[
'views'][
'translate'] = $tab;
1664 LinkTarget $oldLinkTarget,
1665 LinkTarget $newLinkTarget,
1666 UserIdentity $userIdentity,
1670 RevisionRecord $revisionRecord
1672 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $userIdentity );
1677 if ( defined(
'MEDIAWIKI_JOB_RUNNER' ) && $user->equals( FuzzyBot::getUser() ) ) {
1681 $oldTitle = Title::newFromLinkTarget( $oldLinkTarget );
1682 $newTitle = Title::newFromLinkTarget( $newLinkTarget );
1684 foreach ( [ $oldTitle, $newTitle ] as $title ) {
1687 if ( !$handle->isValid() || $handle->isDoc() ) {
1691 $group = $handle->getGroup();
1696 $language = $handle->getCode();
1699 if ( $language ===
'' ) {
1705 if ( $group !== $groupLast ) {
1706 $groupLast = $group;
1707 $page = TranslatablePage::newFromTitle( $group->getTitle() );
1708 self::updateTranslationPage( $page, $language, $user, 0, $reason );
1731 $title = $unit->getTitle();
1733 if ( !$handle->isValid() ) {
1737 $group = $handle->getGroup();
1742 $mwServices = MediaWikiServices::getInstance();
1745 $mwServices->getJobQueueGroup()->push(
1746 RebuildMessageGroupStatsJob::newRefreshGroupsJob( [ $group->getId() ] )
1750 if ( self::$isDeleteTranslatableBundleJobRunning ) {
1754 $target = $group->getTitle();
1755 $langCode = $handle->getCode();
1756 $fname = __METHOD__;
1758 $dbw = $mwServices->getConnectionProvider()->getPrimaryDatabase();
1759 $callback =
function () use (
1768 $translationPageTitle = $target->getSubpage( $langCode );
1771 if ( !$translationPageTitle || !$translationPageTitle->exists( IDBAccessObject::READ_LATEST ) ) {
1775 $dbw->startAtomic( $fname );
1777 $page = TranslatablePage::newFromTitle( $target );
1779 if ( !$handle->isDoc() ) {
1780 $unitTitle = $handle->getTitle();
1782 self::updateTranslationPage(
1783 $page, $langCode, $user, 0, $reason, RenderTranslationPageJob::ACTION_DELETE, $unitTitle
1787 $dbw->endAtomic( $fname );
1790 $dbw->onTransactionCommitOrIdle( $callback, __METHOD__ );
1798 foreach ( $titles as $index => $title ) {
1800 if ( Utilities::isTranslationPage( $handle ) ) {
1801 unset( $titles[ $index ] );
1811 foreach ( $titles as $index => $title ) {
1814 TranslatablePage::isSourcePage( $title ) ||
1815 Utilities::isTranslationPage( $handle )
1817 unset( $titles[ $index ] );
1822 public static function getSpecialManageMessageGroupSubscriptionsLink(
1827 'pagelink' => SpecialPage::getTitleFor(
'ManageMessageGroupSubscriptions' )->getPrefixedText()
1837 if ( !Utilities::isTranslationPage( $handle ) ) {
1840 $code = $handle->getCode();
1841 $categories = $linksUpdate->getParserOutput()->getCategoryNames();
1842 $editSummary = wfMessage(
1843 'translate-category-summary',
1844 $linksUpdate->getTitle()->getPrefixedText()
1845 )->inContentLanguage()->text();
1846 foreach ( $categories as $category ) {
1847 $categoryTitle = Title::makeTitle( NS_CATEGORY, $category );
1852 $categoryTranslationPage = TranslatablePage::isTranslationPage( $categoryTitle );
1854 $categoryTranslationPage
1855 && $categoryHandle->getCode() == $code
1856 && !$categoryTitle->exists()
1858 self::updateTranslationPage(
1859 $categoryTranslationPage,
1861 FuzzyBot::getUser(),
1864 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->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}, '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 or translation pages by any means other than our own move tools.
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.