3namespace MediaWiki\Extension\Translate\PageTranslation;
8use MediaWiki\CommentStore\CommentStoreComment;
9use MediaWiki\Config\Config;
10use MediaWiki\Content\Content;
11use MediaWiki\Content\TextContent;
12use MediaWiki\Context\IContextSource;
13use MediaWiki\Context\RequestContext;
14use MediaWiki\Deferred\DeferredUpdates;
15use MediaWiki\Deferred\LinksUpdate\LinksUpdate;
24use MediaWiki\Html\Html;
25use MediaWiki\Language\Language;
26use MediaWiki\Language\LanguageCode;
27use MediaWiki\Languages\LanguageNameUtils;
28use MediaWiki\Linker\LinkTarget;
29use MediaWiki\Logger\LoggerFactory;
30use MediaWiki\MainConfigNames;
31use MediaWiki\MediaWikiServices;
32use MediaWiki\Output\OutputPage;
33use MediaWiki\Page\PageIdentity;
34use MediaWiki\Page\PageReference;
35use MediaWiki\Parser\Parser;
36use MediaWiki\Parser\ParserOutput;
37use MediaWiki\Parser\PPFrame;
38use MediaWiki\ResourceLoader\Context;
39use MediaWiki\Revision\MutableRevisionRecord;
40use MediaWiki\Revision\RenderedRevision;
41use MediaWiki\Revision\RevisionRecord;
42use MediaWiki\Revision\SlotRecord;
43use MediaWiki\SpecialPage\SpecialPage;
44use MediaWiki\Status\Status;
45use MediaWiki\Storage\EditResult;
46use MediaWiki\StubObject\StubUserLang;
47use MediaWiki\Title\Title;
48use MediaWiki\User\User;
49use MediaWiki\User\UserIdentity;
52use Wikimedia\Rdbms\IDBAccessObject;
53use Wikimedia\ScopedCallback;
64 private const PAGEPROP_HAS_LANGUAGES_TAG =
'translate-has-languages-tag';
66 public static $allowTargetEdit =
false;
70 public static $renderingContext =
false;
72 private static $languageLinkData = [];
81 public static function renderTagPage( $wikitextParser, &$text, $state ): void {
82 if ( $text === null ) {
90 if ( $wikitextParser->getOptions()->getInterfaceMessage() ) {
96 if ( $wikitextParser->getOptions()->getIsSectionPreview() ) {
97 $translatablePageParser = Services::getInstance()->getTranslatablePageParser();
98 $text = $translatablePageParser->cleanupTags( $text );
102 $title = MediaWikiServices::getInstance()
104 ->castFromPageReference( $wikitextParser->getPage() );
114 $wikitextParser->getOutput()->setUnsortedPageProperty(
'translate-is-translation' );
117 self::$renderingContext =
true;
119 $name = $page->getPageDisplayTitle( $code );
121 $name = $wikitextParser->recursivePreprocess( $name );
123 $langConv = MediaWikiServices::getInstance()->getLanguageConverterFactory()
124 ->getLanguageConverter( $wikitextParser->getTargetLanguage() );
125 $name = $langConv->convert( $name );
126 $wikitextParser->getOutput()->setDisplayTitle( $name );
128 self::$renderingContext =
false;
129 }
catch ( Exception $e ) {
131 'T302754 Failed to set display title for page {title}',
133 'title' => $title->getPrefixedDBkey(),
135 'pageid' => $title->getId(),
144 'languagecode' => $code,
145 'messagegroupid' => $page->getMessageGroupId(),
146 'sourcepagetitle' => [
147 'namespace' => $page->getTitle()->getNamespace(),
148 'dbkey' => $page->getTitle()->getDBkey()
152 $wikitextParser->getOutput()->setExtensionData(
'translate-translation-page', $extensionData );
154 $wikitextParser->getOutput()->setExtensionData(
'Translate-noeditsection',
true );
165 $translatablePageParser =
Services::getInstance()->getTranslatablePageParser();
167 if ( $translatablePageParser->containsMarkup( $text ) ) {
169 $parserOutput = $translatablePageParser->parse( $text );
171 $text = $parserOutput->sourcePageTextForRendering(
172 $wikitextParser->getTargetLanguage()
175 wfDebug(
'ParsingFailure caught; expected' );
181 $text = $unit->getTextForTrans();
196 if ( $out->getExtensionData(
'Translate-noeditsection' ) ) {
197 $options[
'enableSectionEditLinks'] =
false;
213 ?LinkTarget $contextLink,
214 ?LinkTarget $templateLink,
216 ?RevisionRecord &$revRecord
218 if ( !$templateLink ) {
222 $templateTitle = Title::castFromLinkTarget( $templateLink );
224 $templateTranslationPage = TranslatablePage::isTranslationPage( $templateTitle );
225 if ( $templateTranslationPage ) {
228 $revRecord = $templateTranslationPage->getRevisionRecordWithFallback();
231 LoggerFactory::getInstance( LogNames::MAIN )->warning(
232 "T323863: Could not fetch any revision record for '{groupid}'",
233 [
'groupid' => $templateTranslationPage->getMessageGroupId() ]
239 if ( !TranslatablePage::isSourcePage( $templateTitle ) ) {
243 $translatableTemplatePage = TranslatablePage::newFromTitle( $templateTitle );
245 if ( !( $translatableTemplatePage->supportsTransclusion() ??
false ) ) {
250 $store = MediaWikiServices::getInstance()->getRevisionStore();
252 if ( $contextLink ) {
254 $templateTranslationTitle = $templateTitle->getSubpage(
255 Title::castFromLinkTarget( $contextLink )->getPageLanguage()->getCode()
258 if ( $templateTranslationTitle ) {
259 if ( $templateTranslationTitle->exists() ) {
261 $revRecord = $store->getRevisionByTitle( $templateTranslationTitle );
266 $revRecord =
new MutableRevisionRecord( $templateTranslationTitle );
274 $sourceTemplateTitle = $templateTitle->getSubpage(
275 $translatableTemplatePage->getMessageGroup()->getSourceLanguage()
277 if ( $sourceTemplateTitle && $sourceTemplateTitle->exists() ) {
278 $revRecord = $store->getRevisionByTitle( $sourceTemplateTitle );
291 if ( TranslatablePage::isTranslationPage( $title ) ) {
292 [ , $code ] = Utilities::figureMessage( $title->getText() );
293 $pageLang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $code );
305 if ( TranslatablePage::isSourcePage( $title ) ) {
306 $msg = wfMessage(
'translate-edit-tag-warning' )->inContentLanguage();
307 if ( !$msg->isDisabled() ) {
308 $notices[
'translate-tag'] = $msg->parseAsBlock();
311 $notices[] = Html::warningBox(
312 wfMessage(
'tps-edit-sourcepage-text' )->parse(),
313 'translate-edit-documentation'
318 $request = RequestContext::getMain()->getRequest();
319 if ( $request->getVal(
'action' ) ===
'visualeditor' &&
320 $request->getVal(
'paction' ) !==
'wikitext'
322 $notices[] = Html::warningBox(
323 wfMessage(
'tps-edit-sourcepage-ve-warning-limited-text' )->parse(),
324 'translate-edit-documentation'
336 global $wgTranslatePageTranslationULS;
338 $title = $out->getTitle();
339 $isSource = TranslatablePage::isSourcePage( $title );
340 $isTranslation = TranslatablePage::isTranslationPage( $title );
342 if ( $isSource || $isTranslation ) {
343 if ( $wgTranslatePageTranslationULS ) {
344 $out->addModules(
'ext.translate.pagetranslation.uls' );
349 $out->addModuleStyles(
'ext.translate.edit.documentation.styles' );
352 $out->addModuleStyles(
'ext.translate' );
354 $out->addJsConfigVars(
'wgTranslatePageTranslation', $isTranslation ?
'translation' :
'source' );
365 return !TranslatablePage::isTranslationPage( $out->getTitle() );
381 TextContent $content,
388 if ( $user->equals( FuzzyBot::getUser() ) ) {
398 $page = TranslatablePage::newFromTitle( $group->getTitle() );
401 if ( !$handle->
isDoc() ) {
403 DeferredUpdates::addCallableUpdate(
404 function () use ( $page, $code, $user, $flags, $summary, $handle ) {
406 self::updateTranslationPage( $page, $code, $user, $flags, $summary,
null, $unitTitle );
412 private static function updateTranslationPage(
418 ?
string $triggerAction =
null,
419 ?Title $unitTitle =
null
421 $source = $page->getTitle();
422 $target = $source->getSubpage( $code );
423 $mwInstance = MediaWikiServices::getInstance();
426 $flags &= ~EDIT_NEW & ~EDIT_UPDATE;
429 $unitTitleText = $unitTitle ? $unitTitle->getPrefixedText() :
null;
430 $job = RenderTranslationPageJob::newJob( $target, $triggerAction, $unitTitleText );
431 $job->setUser( $user );
432 $job->setSummary( $summary );
433 $job->setFlags( $flags );
434 $mwInstance->getJobQueueGroup()->push( $job );
438 $wikiPageFactory = $mwInstance->getWikiPageFactory();
439 foreach ( $pages as $title ) {
440 if ( $title->equals( $target ) ) {
445 $wikiPage = $wikiPageFactory->newFromTitle( $title );
446 $wikiPage->doPurge();
448 $sourceWikiPage = $wikiPageFactory->newFromTitle( $source );
449 $sourceWikiPage->doPurge();
457 $variableIDs[] =
'translatablepage';
465 array &$variableCache,
470 switch ( $magicWordId ) {
471 case 'translatablepage':
472 $pageStatus = self::getTranslatablePageStatus( $parser->getPage() );
473 $ret = $pageStatus !==
null ? $pageStatus[
'page']->getTitle()->getPrefixedText() :
'';
474 $variableCache[$magicWordId] = $ret;
485 public static function languages( $data, $params, $parser ) {
486 global $wgPageTranslationLanguageList;
488 if ( $wgPageTranslationLanguageList ===
'sidebar-only' ) {
492 self::$renderingContext =
true;
493 $context =
new ScopedCallback(
static function () {
494 self::$renderingContext =
false;
499 $parser->getOutput()->setUnsortedPageProperty( self::PAGEPROP_HAS_LANGUAGES_TAG );
501 $currentPage = $parser->getPage();
502 $pageStatus = self::getTranslatablePageStatus( $currentPage );
503 if ( !$pageStatus ) {
507 $page = $pageStatus[
'page' ];
508 $status = $pageStatus[
'languages' ];
515 $userLang = $parser->getOptions()->getUserLangObj();
516 $userLangCode = $userLang->getCode();
520 $sourceLanguage = $pageTitle->getPageLanguage()->getCode();
523 $langFactory = MediaWikiServices::getInstance()->getLanguageFactory();
524 foreach ( $status as $code => $percent ) {
526 $name = Utilities::getLanguageName( $code, LanguageNameUtils::AUTONYMS );
529 $suffix = ( $code === $sourceLanguage ) ?
'' :
"/$code";
530 $targetTitleString = $pageTitle->getDBkey() . $suffix;
531 $subpage = Title::makeTitle( $pageTitle->getNamespace(), $targetTitleString );
534 if ( $code === $userLangCode ) {
535 $classes[] =
'mw-pt-languages-ui';
538 $linker = $parser->getLinkRenderer();
539 $lang = $langFactory->getLanguage( $code );
540 if ( $currentPage->isSamePageAs( $subpage ) ) {
541 $classes[] =
'mw-pt-languages-selected';
542 $classes = array_merge( $classes, self::tpProgressIcon( (
float)$percent ) );
545 'lang' => $lang->getHtmlCode(),
546 'dir' => $lang->getDir(),
549 $contents = Html::element(
'span', $attribs, $name );
550 } elseif ( $subpage->isKnown() ) {
552 if ( !is_string( $pagename ) ) {
553 $pagename = $subpage->getPrefixedText();
556 $classes = array_merge( $classes, self::tpProgressIcon( (
float)$percent ) );
558 $title = wfMessage(
'tpt-languages-nonzero' )
559 ->page( $parser->getPage() )
560 ->inLanguage( $userLang )
561 ->params( $pagename )
562 ->numParams( 100 * $percent )
567 'lang' => $lang->getHtmlCode(),
568 'dir' => $lang->getDir(),
571 $contents = $linker->makeKnownLink( $subpage, $name, $attribs );
576 $specialTranslateTitle = SpecialPage::getTitleFor(
'Translate' );
586 'title' => wfMessage(
'tpt-languages-zero' )
587 ->page( $parser->getPage() )
588 ->inLanguage( $userLang )
591 'lang' => $lang->getHtmlCode(),
592 'dir' => $lang->getDir(),
594 $contents = $linker->makeKnownLink( $specialTranslateTitle, $name, $attribs, $params );
596 $languages[ $name ] = Html::rawElement(
'li', [], $contents );
601 $languages = array_values( $languages );
602 $languages = implode(
"\n", $languages );
604 $out = Html::openElement(
'div', [
605 'class' =>
'mw-pt-languages noprint navigation-not-searchable',
606 'lang' => $userLang->getHtmlCode(),
607 'dir' => $userLang->getDir()
609 $out .= Html::rawElement(
'div', [
'class' =>
'mw-pt-languages-label' ],
610 wfMessage(
'tpt-languages-legend' )
611 ->page( $parser->getPage() )
612 ->inLanguage( $userLang )
615 $out .= Html::rawElement(
617 [
'class' =>
'mw-pt-languages-list' ],
620 $out .= Html::closeElement(
'div' );
622 $parser->getOutput()->addModuleStyles( [
623 'ext.translate.tag.languages',
635 private static function tpProgressIcon(
float $percent ) {
636 $classes = [
'mw-pt-progress' ];
638 if ( $percent < 15 ) {
639 $classes[] =
'mw-pt-progress--low';
640 } elseif ( $percent < 70 ) {
641 $classes[] =
'mw-pt-progress--med';
642 } elseif ( $percent < 100 ) {
643 $classes[] =
'mw-pt-progress--high';
645 $classes[] =
'mw-pt-progress--complete';
654 private static function getTranslatablePageStatus( ?PageReference $pageReference ): ?array {
655 if ( $pageReference === null ) {
658 $title = Title::newFromPageReference( $pageReference );
660 $page = TranslatablePage::newFromTitle( $title );
662 $page = TranslatablePage::isTranslationPage( $title );
665 if ( $page ===
false || $page->
getMarkedTag() ===
null ) {
669 $status = $page->getTranslationPercentages();
674 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
676 $priorityLanguages = $messageGroupMetadata->get( $page->
getMessageGroupId(),
'prioritylangs' );
677 if ( (
string)$priorityLanguages !==
'' ) {
678 $status += array_fill_keys( explode(
',', $priorityLanguages ), 0 );
683 'languages' => $status
693 global $wgPageTranslationLanguageList;
695 if ( $wgPageTranslationLanguageList ===
'tag-only' ) {
699 if ( $wgPageTranslationLanguageList ===
'sidebar-fallback' ) {
700 $pageProps = MediaWikiServices::getInstance()->getPageProps();
701 $languageProp = $pageProps->getProperties( $title, self::PAGEPROP_HAS_LANGUAGES_TAG );
702 if ( $languageProp !== [] ) {
709 $status = self::getTranslatablePageStatus( $title );
714 self::$renderingContext =
true;
715 $context =
new ScopedCallback(
static function () {
716 self::$renderingContext =
false;
719 $page = $status[
'page' ];
720 $languages = $status[
'languages' ];
721 $mwServices = MediaWikiServices::getInstance();
722 $en = $mwServices->getLanguageFactory()->getLanguage(
'en' );
725 $lb = $mwServices->getLinkBatchFactory()->newLinkBatch();
726 foreach ( array_keys( $languages ) as $code ) {
727 $title = $page->getTitle()->getSubpage( $code );
728 $lb->addObj( $title );
731 $languageNameUtils = $mwServices->getLanguageNameUtils();
732 foreach ( $languages as $code => $percentage ) {
733 $title = $page->getTitle()->getSubpage( $code );
734 $placeholderValue =
"$code-x-pagetranslation:{$title->getPrefixedText()}";
735 $translatedName = $page->getPageDisplayTitle( $code ) ?: $title->getPrefixedText();
737 if ( $title->exists() ) {
738 $href = $title->getLocalURL();
739 $classes = self::tpProgressIcon( (
float)$percentage );
740 $titleAttribute = wfMessage(
'tpt-languages-nonzero' )
741 ->params( $translatedName )
742 ->numParams( 100 * $percentage );
744 $href = SpecialPage::getTitleFor(
'Translate' )->getLocalURL( [
745 'group' => $page->getMessageGroupId(),
748 $classes = [
'mw-pt-progress--none' ];
749 $titleAttribute = wfMessage(
'tpt-languages-zero' );
752 self::$languageLinkData[ $placeholderValue ] = [
755 'classes' => $classes,
756 'autonym' => $en->ucfirst( $languageNameUtils->getLanguageName( $code ) ),
757 'title' => $titleAttribute,
761 $languageLinks[] = $placeholderValue;
778 $data = self::$languageLinkData[$link[
'text']] ??
null;
783 $link[
'class'] .=
' interwiki-pagetranslation ' . implode(
' ', $data[
'classes'] );
784 $link[
'href'] = $data[
'href'];
785 $link[
'text'] = $data[
'autonym'];
786 $link[
'title'] = $data[
'title']->inLanguage( $out->getLanguage()->getCode() )->text();
787 $link[
'lang'] = LanguageCode::bcp47( $data[
'language'] );
788 $link[
'hreflang'] = LanguageCode::bcp47( $data[
'language'] );
790 $out->addModuleStyles(
'ext.translate.tag.languages' );
808 $syntaxErrorStatus = self::tpSyntaxError( $context->getTitle(), $content );
810 if ( $syntaxErrorStatus ) {
811 $status->merge( $syntaxErrorStatus );
812 return $syntaxErrorStatus->isGood();
818 private static function tpSyntaxError( ?PageIdentity $page, ?Content $content ): ?Status {
819 if ( !$page || !self::isAllowedContentModel( $content, $page ) ) {
823 '@phan-var TextContent $content';
824 $text = $content->getText();
827 $text = TextContent::normalizeLineEndings( $text );
828 $status = Status::newGood();
829 $parser = Services::getInstance()->getTranslatablePageParser();
830 if ( $parser->containsMarkup( $text ) ) {
832 $parser->parse( $text );
833 }
catch ( ParsingFailure $e ) {
834 $status->fatal( $e->getMessageSpecification() );
853 RenderedRevision $renderedRevision,
855 CommentStoreComment $summary,
859 $content = $renderedRevision->getRevision()->getContent( SlotRecord::MAIN );
861 $status = self::tpSyntaxError(
862 $renderedRevision->getRevision()->getPage(),
867 $hookStatus->merge( $status );
868 return $status->isGood();
886 UserIdentity $userIdentity,
889 RevisionRecord $revisionRecord,
890 EditResult $editResult
892 $content = $wikiPage->getContent();
895 if ( !self::isAllowedContentModel( $content, $wikiPage ) ) {
899 '@phan-var TextContent $content';
900 $text = $content->getText();
902 $parser = Services::getInstance()->getTranslatablePageParser();
903 if ( $parser->containsMarkup( $text ) ) {
905 $page = TranslatablePage::newFromTitle( $wikiPage->getTitle() );
910 $tpStatusUpdater = Services::getInstance()->getTranslatablePageStore();
911 $tpStatusUpdater->performStatusUpdate( $wikiPage->getTitle() );
930 $parentId = $rev->getParentId();
931 if ( $parentId === 0 || $parentId ===
null ) {
936 $prevRev = MediaWikiServices::getInstance()
937 ->getRevisionLookup()
938 ->getRevisionById( $parentId );
940 if ( !$prevRev || !$rev->hasSameContent( $prevRev ) ) {
945 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
946 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
947 $bundle = $bundleFactory->getBundle( $title );
950 $bundleStore = $bundleFactory->getStore( $bundle );
951 $bundleStore->handleNullRevisionInsert( $bundle, $rev );
976 if ( !$handle->isPageTranslation() || $action ===
'read' ) {
983 if ( $handle->isValid() ) {
984 $group = $handle->getGroup();
985 $groupId = $group->getId();
986 $permissionTitleCheck =
null;
989 $permissionTitleCheck = $group->getTitle();
993 $permissionTitleCheck = Title::newFromID( $group->getBundlePageId() );
996 if ( $permissionTitleCheck ) {
997 if ( $handle->getCode() === $group->getSourceLanguage() && !$user->equals( FuzzyBot::getUser() ) ) {
1001 $allowedActionList = [
1002 'deleterevision',
'suppressrevision',
'viewsuppressed',
1004 if ( !in_array( $action, $allowedActionList ) ) {
1005 $result = [
'tpt-cant-edit-source-language', $permissionTitleCheck ];
1010 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1011 if ( $permissionManager->isBlockedFrom( $user, $permissionTitleCheck ) ) {
1012 $block = $user->getBlock();
1014 $error =
new UserBlockedError( $block, $user );
1015 $errorMessage = $error->getMessageObject();
1016 $result = array_merge( [ $errorMessage->getKey() ], $errorMessage->getParams() );
1025 if ( $action !==
'create' ) {
1029 if ( !$handle->isValid() ) {
1035 $translatablePage = self::checkTranslatablePageSlow( $title );
1036 MediaWikiServices::getInstance()->getStatsFactory()
1037 ->withComponent(
'Translate' )
1038 ->getCounter(
'slow_translatable_page_check' )
1039 ->setLabel(
'valid', $translatablePage ?
'yes' :
'no' )
1042 if ( $translatablePage ) {
1043 $groupId = $translatablePage->getMessageGroupId();
1050 $error = self::getTranslationRestrictions( $handle, $groupId );
1051 $result = $error ?: $result;
1052 return $error === [];
1056 LoggerFactory::getInstance( LogNames::MAIN )->info(
1057 'Unknown translation page: {title}',
1058 [
'title' => $title->getPrefixedDBkey() ]
1060 $result = [
'tpt-unknown-page' ];
1064 private static function checkTranslatablePageSlow( LinkTarget $unit ): ?
TranslatablePage {
1066 $translationPageTitle = Title::newFromText(
1067 $parts[
'sourcepage' ] .
'/' . $parts[
'language' ]
1069 if ( !$translationPageTitle ) {
1073 $translatablePage = TranslatablePage::isTranslationPage( $translationPageTitle );
1074 if ( !$translatablePage ) {
1078 $factory = Services::getInstance()->getTranslationUnitStoreFactory();
1079 $store = $factory->getReader( $translatablePage->getTitle() );
1080 $units = $store->getNames();
1082 if ( !in_array( $parts[
'section' ], $units ) ) {
1086 return $translatablePage;
1096 private static function getTranslationRestrictions( MessageHandle $handle, $groupId ) {
1097 global $wgTranslateDocumentationLanguageCode;
1100 if ( $handle->getCode() === $wgTranslateDocumentationLanguageCode ) {
1104 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
1106 $force = $messageGroupMetadata->get( $groupId,
'priorityforce' );
1107 if ( $force !==
'on' ) {
1112 $languages = $messageGroupMetadata->get( $groupId,
'prioritylangs' );
1113 $reason = $messageGroupMetadata->get( $groupId,
'priorityreason' );
1114 if ( !$languages ) {
1116 return [
'tpt-translation-restricted-no-priority-languages', $reason ];
1118 return [
'tpt-translation-restricted-no-priority-languages-no-reason' ];
1121 $filter = array_flip( explode(
',', $languages ) );
1122 if ( !isset( $filter[$handle->getCode()] ) ) {
1124 return [
'tpt-translation-restricted', $reason ];
1127 return [
'tpt-translation-restricted-no-reason' ];
1143 if ( self::$allowTargetEdit ) {
1148 'read',
'deletedtext',
'deletedhistory',
1149 'deleterevision',
'suppressrevision',
'viewsuppressed',
1153 $needsPageTranslationRight = in_array( $action, [
'delete',
'undelete' ] );
1154 if ( in_array( $action, $inclusionList ) ||
1155 ( $needsPageTranslationRight && $user->isAllowed(
'pagetranslation' ) )
1160 $page = TranslatablePage::isTranslationPage( $title );
1162 $mwService = MediaWikiServices::getInstance();
1163 if ( $needsPageTranslationRight ) {
1164 $context = RequestContext::getMain();
1165 $statusFormatter = $mwService->getFormatterFactory()->getStatusFormatter( $context );
1166 $permissionError = $mwService->getPermissionManager()
1167 ->newFatalPermissionDeniedStatus(
'pagetranslation', $context );
1168 $result = $statusFormatter->getMessage( $permissionError );
1172 [ , $code ] = Utilities::figureMessage( $title->getText() );
1174 $translationUrl = $mwService->getUrlUtils()->expand(
1180 ':' . $page->
getTitle()->getPrefixedText(),
1202 $title = $article->getTitle();
1203 $bundle = Services::getInstance()->getTranslatableBundleFactory()->getBundle( $title );
1204 $isDeletableBundle = $bundle && $bundle->isDeletable();
1205 if ( $isDeletableBundle || TranslatablePage::isTranslationPage( $title ) ) {
1206 $new = SpecialPage::getTitleFor(
1207 'PageTranslationDeletePage',
1208 $title->getPrefixedText()
1210 $out->redirect( $new->getFullURL() );
1224 if ( $article->getOldID() ) {
1228 $articleTitle = $article->getTitle();
1229 $transPage = TranslatablePage::isTranslationPage( $articleTitle );
1230 $context = $article->getContext();
1232 self::translationPageHeader( $context, $transPage );
1234 $viewTranslatablePage = Services::getInstance()->getTranslatablePageView();
1235 $user = $context->getUser();
1236 if ( $viewTranslatablePage->canDisplayTranslationSettingsBanner( $articleTitle, $user ) ) {
1237 $output = $context->getOutput();
1238 $pageUrl = SpecialPage::getTitleFor(
'PageTranslation' )->getFullURL( [
1240 'target' => $articleTitle->getPrefixedDBkey(),
1244 $context->msg(
'pt-cta-mark-translation', $pageUrl )->parse(),
1245 'translate-cta-pt-mark'
1249 self::sourcePageHeader( $context );
1254 private static function sourcePageHeader( IContextSource $context ) {
1255 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1257 $language = $context->getLanguage();
1258 $title = $context->getTitle();
1260 $page = TranslatablePage::newFromTitle( $title );
1264 $latest = $title->getLatestRevID();
1267 if ( $marked && $context->getUser()->isAllowed(
'translate' ) ) {
1268 $actions[] = self::getTranslateLink( $context, $page,
null );
1271 $hasChanges = $ready === $latest && $marked !== $latest;
1272 if ( $hasChanges ) {
1273 $diffUrl = $title->getFullURL( [
'oldid' => $marked,
'diff' => $latest ] );
1275 if ( $context->getUser()->isAllowed(
'pagetranslation' ) ) {
1276 $pageTranslation = SpecialPage::getTitleFor(
'PageTranslation' );
1277 $params = [
'target' => $title->getPrefixedText(),
'do' =>
'mark' ];
1279 if ( $marked ===
null ) {
1281 $linkDesc = $context->msg(
'translate-tag-markthis' )->text();
1282 $actions[] = $linker->makeKnownLink( $pageTranslation, $linkDesc, [], $params );
1284 $markUrl = $pageTranslation->getFullURL( $params );
1285 $actions[] = $context->msg(
'translate-tag-markthisagain', $diffUrl, $markUrl )
1289 $actions[] = $context->msg(
'translate-tag-hasnew', $diffUrl )->parse();
1293 if ( !count( $actions ) ) {
1297 $header = Html::rawElement(
1300 'class' =>
'mw-pt-translate-header noprint nomobile',
1301 'dir' => $language->getDir(),
1302 'lang' => $language->getHtmlCode(),
1304 $language->semicolonList( $actions )
1307 $context->getOutput()->addHTML( $header );
1310 private static function getTranslateLink(
1311 IContextSource $context,
1312 TranslatablePage $page,
1315 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1317 return $linker->makeKnownLink(
1318 SpecialPage::getTitleFor(
'Translate' ),
1319 $context->msg(
'translate-tag-translate-link-desc' )->text(),
1322 'group' => $page->getMessageGroupId(),
1323 'language' => $langCode,
1326 'action_source' =>
'translate_page'
1331 private static function translationPageHeader( IContextSource $context, TranslatablePage $page ) {
1332 global $wgTranslateKeepOutdatedTranslations;
1334 $title = $context->getTitle();
1335 if ( !$title->exists() ) {
1339 [ , $code ] = Utilities::figureMessage( $title->getText() );
1342 $pers = $page->getTranslationPercentages();
1344 if ( isset( $pers[$code] ) ) {
1345 $per = $pers[$code] * 100;
1348 $language = $context->getLanguage();
1349 $output = $context->getOutput();
1351 if ( $page->getSourceLanguageCode() === $code ) {
1353 $msg = self::getTranslateLink( $context, $page, $language->getCode() );
1355 $mwService = MediaWikiServices::getInstance();
1357 $translationUrl = $mwService->getUrlUtils()->expand(
1358 $page->getTranslationUrl( $code ), PROTO_RELATIVE
1361 $msg = $context->msg(
'tpt-translation-intro',
1363 ':' . $page->getTitle()->getPrefixedText(),
1364 $language->formatNum( $per )
1368 $header = Html::rawElement(
1371 'class' =>
'mw-pt-translate-header noprint',
1372 'dir' => $language->getDir(),
1373 'lang' => $language->getHtmlCode(),
1378 $output->addHTML( $header );
1380 if ( $wgTranslateKeepOutdatedTranslations ) {
1381 $groupId = $page->getMessageGroupId();
1383 $stats = MessageGroupStats::forItem( $groupId, $code );
1384 if ( $stats[MessageGroupStats::FUZZY] ) {
1386 $wrap = Html::rawElement(
1389 'class' =>
'mw-pt-translate-header',
1390 'dir' => $language->getDir(),
1391 'lang' => $language->getHtmlCode()
1393 '<span class="mw-translate-fuzzy">$1</span>'
1396 $output->wrapWikiMsg( $wrap, [
'tpt-translation-intro-fuzzy' ] );
1401 private static function isAllowedContentModel( Content $content, PageReference $page ): bool {
1402 $config = MediaWikiServices::getInstance()->getMainConfig();
1403 $allowedModels = $config->get(
'PageTranslationAllowedContentModels' );
1404 $contentModel = $content->getModel();
1405 $allowed = (bool)( $allowedModels[$contentModel] ??
false );
1408 if ( $allowed && !$content instanceof TextContent ) {
1409 LoggerFactory::getInstance( LogNames::MAIN )->error(
1410 'Expected {title} to have content of type TextContent, got {contentType}. ' .
1411 '$wgPageTranslationAllowedContentModels is incorrectly configured with a non-text content model.',
1413 'title' => (
string)$page,
1414 'contentType' => get_class( $content )
1428 $movePageSpec = $list[
'Movepage'] ??
null;
1431 if ( $movePageSpec ===
null ) {
1435 $list[
'Movepage'] = [
1436 'class' => MoveTranslatableBundleSpecialPage::class,
1439 'PermissionManager',
1440 'Translate:TranslatableBundleMover',
1441 'Translate:TranslatableBundleFactory',
1459 if ( $action ===
'read' ) {
1463 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance( CACHE_ANYTHING );
1464 $key = $cache->makeKey(
'pt-lock', sha1( $title->getPrefixedText() ) );
1465 if ( $cache->get( $key ) ===
'locked' ) {
1466 $result = [
'pt-locked-page' ];
1482 $linker = MediaWikiServices::getInstance()->getLinkRenderer();
1484 $isTranslationPage = TranslatablePage::isTranslationPage( $out->getTitle() );
1485 if ( !$isTranslationPage
1486 && !TranslatablePage::isSourcePage( $out->getTitle() )
1492 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1494 $out->isArticle() &&
1495 $nsInfo->hasSubpages( $out->getTitle()->getNamespace() )
1497 $ptext = $out->getTitle()->getPrefixedText();
1498 $links = explode(
'/', $ptext );
1499 if ( count( $links ) > 1 ) {
1500 array_pop( $links );
1501 if ( $isTranslationPage ) {
1503 array_pop( $links );
1508 $sitedir = $skin->getLanguage()->getDir();
1510 foreach ( $links as $link ) {
1511 $growinglink .= $link;
1513 $linkObj = Title::newFromText( $growinglink );
1515 if ( $linkObj && $linkObj->isKnown() ) {
1516 $getlink = $linker->makeKnownLink(
1517 SpecialPage::getTitleFor(
'MyLanguage', $growinglink ),
1524 $subpages .= $skin->msg(
'pipe-separator' )->escaped();
1526 $subpages .=
'< ';
1529 $subpages .= Html::rawElement(
'bdi', [
'dir' => $sitedir ], $getlink );
1535 $growinglink .=
'/';
1552 $title = $skin->getTitle();
1554 $code = $handle->getCode();
1555 $page = TranslatablePage::isTranslationPage( $title );
1557 if ( !$page || $page->getSourceLanguageCode() === $code ) {
1561 $user = $skin->getUser();
1562 if ( isset( $tabs[
'views'][
'edit'] ) ) {
1564 $tabs[
'views'][
'edit'][
'text'] = $skin->msg(
'tpt-tab-translate' )->text();
1565 $tabs[
'views'][
'edit'][
'href'] = $page->getTranslationUrl( $code );
1566 } elseif ( $user->isAllowed(
'translate' ) ) {
1567 $mwInstance = MediaWikiServices::getInstance();
1568 $namespaceProtection = $mwInstance->getMainConfig()->get( MainConfigNames::NamespaceProtection );
1569 $permissionManager = $mwInstance->getPermissionManager();
1571 !$permissionManager->userHasAllRights(
1572 $user, ...(array)( $namespaceProtection[ NS_TRANSLATIONS ] ?? [] )
1579 'text' => $skin->msg(
'tpt-tab-translate' )->text(),
1580 'href' => $page->getTranslationUrl( $code ),
1584 $viewsourcePos = array_keys( array_keys( $tabs[
'views'] ),
'viewsource',
true )[0] ??
null;
1586 if ( $viewsourcePos !==
null ) {
1589 array_splice( $tabs[
'views'], $viewsourcePos, 1, [
'translate' => $tab ] );
1593 $tabs[
'views'][
'translate'] = $tab;
1611 LinkTarget $oldLinkTarget,
1612 LinkTarget $newLinkTarget,
1613 UserIdentity $userIdentity,
1617 RevisionRecord $revisionRecord
1619 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $userIdentity );
1624 if ( defined(
'MEDIAWIKI_JOB_RUNNER' ) && $user->equals( FuzzyBot::getUser() ) ) {
1628 $oldTitle = Title::newFromLinkTarget( $oldLinkTarget );
1629 $newTitle = Title::newFromLinkTarget( $newLinkTarget );
1631 foreach ( [ $oldTitle, $newTitle ] as $title ) {
1634 if ( !$handle->isValid() || $handle->isDoc() ) {
1638 $group = $handle->getGroup();
1643 $language = $handle->getCode();
1646 if ( $language ===
'' ) {
1652 if ( $group !== $groupLast ) {
1653 $groupLast = $group;
1654 $page = TranslatablePage::newFromTitle( $group->getTitle() );
1655 self::updateTranslationPage( $page, $language, $user, 0, $reason );
1678 $title = $unit->getTitle();
1680 if ( !$handle->isValid() ) {
1684 $group = $handle->getGroup();
1689 $mwServices = MediaWikiServices::getInstance();
1692 $mwServices->getJobQueueGroup()->push(
1693 RebuildMessageGroupStatsJob::newRefreshGroupsJob( [ $group->getId() ] )
1697 if ( self::$isDeleteTranslatableBundleJobRunning ) {
1701 $target = $group->getTitle();
1702 $langCode = $handle->getCode();
1703 $fname = __METHOD__;
1705 $dbw = $mwServices->getConnectionProvider()->getPrimaryDatabase();
1706 $callback =
function () use (
1715 $translationPageTitle = $target->getSubpage( $langCode );
1718 if ( !$translationPageTitle || !$translationPageTitle->exists( IDBAccessObject::READ_LATEST ) ) {
1722 $dbw->startAtomic( $fname );
1724 $page = TranslatablePage::newFromTitle( $target );
1726 if ( !$handle->isDoc() ) {
1727 $unitTitle = $handle->getTitle();
1729 self::updateTranslationPage(
1730 $page, $langCode, $user, 0, $reason, RenderTranslationPageJob::ACTION_DELETE, $unitTitle
1734 $dbw->endAtomic( $fname );
1737 $dbw->onTransactionCommitOrIdle( $callback, __METHOD__ );
1745 foreach ( $titles as $index => $title ) {
1747 if ( Utilities::isTranslationPage( $handle ) ) {
1748 unset( $titles[ $index ] );
1758 foreach ( $titles as $index => $title ) {
1761 TranslatablePage::isSourcePage( $title ) ||
1762 Utilities::isTranslationPage( $handle )
1764 unset( $titles[ $index ] );
1769 public static function getSpecialManageMessageGroupSubscriptionsLink(
1774 'pagelink' => SpecialPage::getTitleFor(
'ManageMessageGroupSubscriptions' )->getPrefixedText()
1784 if ( !Utilities::isTranslationPage( $handle ) ) {
1787 $code = $handle->getCode();
1788 $categories = $linksUpdate->getParserOutput()->getCategoryNames();
1789 $editSummary = wfMessage(
1790 'translate-category-summary',
1791 $linksUpdate->getTitle()->getPrefixedText()
1792 )->inContentLanguage()->text();
1793 foreach ( $categories as $category ) {
1794 $categoryTitle = Title::makeTitle( NS_CATEGORY, $category );
1799 $categoryTranslationPage = TranslatablePage::isTranslationPage( $categoryTitle );
1801 $categoryTranslationPage
1802 && $categoryHandle->getCode() == $code
1803 && !$categoryTitle->exists()
1805 self::updateTranslationPage(
1806 $categoryTranslationPage,
1808 FuzzyBot::getUser(),
1811 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(), 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 { 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());}, '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);}]
@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 onParserOutputPostCacheTransform(ParserOutput $out, &$text, array &$options)
Hook: ParserOutputPostCacheTransform.
static onSectionSave(WikiPage $wikiPage, User $user, TextContent $content, $summary, $minor, $flags, MessageHandle $handle)
This is triggered after an edit to translation unit page.
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 translateTab(Skin $skin, array &$tabs)
Converts the edit tab (if exists) for translation pages to translate tab.
static addTranstagAfterSave(WikiPage $wikiPage, UserIdentity $userIdentity, string $summary, int $flags, RevisionRecord $revisionRecord, EditResult $editResult)
Hook: PageSaveComplete.
Represents a parsing output produced by TranslatablePageParser.
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.
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.